#!/usr/bin/env python
# -*- coding: utf-8 -*-

#  This file is part of PlexPy.
#
#  PlexPy is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  PlexPy is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with PlexPy.  If not, see <http://www.gnu.org/licenses/>.

import json
import hashlib
import os
import random
import re
import traceback

import cherrypy
import xmltodict

import plexpy
import datafactory
import graphs
import logger
import plextv
import pmsconnect
import users
import versioncheck


cmd_list = ['getLogs', 'getVersion', 'checkGithub', 'shutdown',
            'getSettings', 'restart', 'update', 'getApikey', 'getHistory',
            'getMetadata', 'getUserips', 'getPlayby', 'getSync']


class Api(object):
    def __init__(self, out='json'):

        self.apikey = None
        self.authenticated = False
        self.cmd = None
        self.kwargs = None
        # For the responses
        self.data = None
        self.msg = None
        self.result_type = 'error'
        # Possible general params
        self.callback = None
        self.out_type = out
        self.debug = None

    def checkParams(self, *args, **kwargs):

        if not plexpy.CONFIG.API_ENABLED:
            self.msg = 'API not enabled'
        elif not plexpy.CONFIG.API_KEY:
            self.msg = 'API key not generated'
        elif len(plexpy.CONFIG.API_KEY) != 32:
            self.msg = 'API key not generated correctly'
        elif 'apikey' not in kwargs:
            self.msg = 'Parameter apikey is required'
        elif kwargs.get('apikey', '') != plexpy.CONFIG.API_KEY:
            self.msg = 'Invalid apikey'
        elif 'cmd' not in kwargs:
            self.msg = 'Parameter %s required. possible commands are: %s' % ', '.join(cmd_list)
        elif 'cmd' in kwargs and kwargs.get('cmd') not in cmd_list:
            self.msg = 'Unknown command, %s possible commands are: %s' % (kwargs.get('cmd', ''), ', '.join(cmd_list))

        # Set default values or remove them from kwargs

        self.callback = kwargs.pop('callback', None)
        self.apikey = kwargs.pop('apikey', None)
        self.cmd = kwargs.pop('cmd', None)
        self.debug = kwargs.pop('debug', False)
        # Allow override for the api.
        self.out_type = kwargs.pop('out_type', 'json')

        if self.apikey == plexpy.CONFIG.API_KEY and plexpy.CONFIG.API_ENABLED and self.cmd in cmd_list:
            self.authenticated = True
            self.msg = None
        elif self.cmd == 'getApikey' and plexpy.CONFIG.API_ENABLED:
            self.authenticated = True
            # Remove the old error msg
            self.msg = None

        self.kwargs = kwargs

    def _responds(self, result_type='success', data=None, msg=''):

        if data is None:
            data = {}
        return {"response": {"result": result_type, "message": msg, "data": data}}

    def _out_as(self, out):

        if self.out_type == 'json':
            cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8'
            try:
                out = json.dumps(out, indent=4, sort_keys=True)
                if self.callback is not None:
                    cherrypy.response.headers['Content-Type'] = 'application/javascript'
                    # wrap with JSONP call if requested
                    out = self.callback + '(' + out + ');'
            # if we fail to generate the output fake an error
            except Exception as e:
                logger.info(u"API :: " + traceback.format_exc())
                out['message'] = traceback.format_exc()
                out['result'] = 'error'
        if self.out_type == 'xml':
            cherrypy.response.headers['Content-Type'] = 'application/xml'
            try:
                out = xmltodict.unparse(out, pretty=True)
            except ValueError as e:
                logger.error('Failed to parse xml result')
                try:
                    out['message'] = e
                    out['result'] = 'error'
                    out = xmltodict.unparse(out, pretty=True)

                except Exception as e:
                    logger.error('Failed to parse xml result error message')
                    out = '''<?xml version="1.0" encoding="utf-8"?>
                                <response>
                                    <message>%s</message>
                                    <data></data>
                                    <result>error</result>
                                </response>
                          ''' % e

        return out

    def fetchData(self):

        logger.info('Recieved API command: %s' % self.cmd)
        if self.cmd and self.authenticated:
            methodtocall = getattr(self, "_" + self.cmd)
            # Let the traceback hit cherrypy so we can
            # see the traceback there
            if self.debug:
                methodtocall(**self.kwargs)
            else:
                try:
                    methodtocall(**self.kwargs)
                except Exception as e:
                    logger.error(traceback.format_exc())

        # Im just lazy, fix me plx
        if self.data or isinstance(self.data, (dict, list)):
            if len(self.data):
                self.result_type = 'success'

        return self._out_as(self._responds(result_type=self.result_type, msg=self.msg, data=self.data))

    def _dic_from_query(self, query):

        myDB = database.DBConnection()
        rows = myDB.select(query)

        rows_as_dic = []

        for row in rows:
            row_as_dic = dict(zip(row.keys(), row))
            rows_as_dic.append(row_as_dic)

        return rows_as_dic

    def _getApikey(self, username='', password=''):
        """ Returns api key, requires username and password is active """

        apikey = hashlib.sha224(str(random.getrandbits(256))).hexdigest()[0:32]
        if plexpy.CONFIG.HTTP_USERNAME and plexpy.CONFIG.HTTP_PASSWORD:
            if username == plexpy.HTTP_USERNAME and password == plexpy.CONFIG.HTTP_PASSWORD:
                if plexpy.CONFIG.API_KEY:
                    self.data = plexpy.CONFIG.API_KEY
                else:
                    self.data = apikey
                    plexpy.CONFIG.API_KEY = apikey
                    plexpy.CONFIG.write()
            else:
                self.msg = 'Authentication is enabled, please add the correct username and password to the parameters'
        else:
            if plexpy.CONFIG.API_KEY:
                self.data = plexpy.CONFIG.API_KEY
            else:
                # Make a apikey if the doesn't exist
                self.data = apikey
                plexpy.CONFIG.API_KEY = apikey
                plexpy.CONFIG.write()

        return self.data

    def _getLogs(self, sort='', search='', order='desc', regex='', **kwargs):
        """
            Returns the log

            Returns [{"response":
                                {"msg": "Hey",
                                 "result": "success"},
                                 "data": [{"time": "29-sept.2015",
                                            "thread: "MainThread",
                                            "msg: "Called x from y",
                                            "loglevel": "DEBUG"
                                           }
                                        ]

                                }
                    ]
        """
        logfile = os.path.join(plexpy.CONFIG.LOG_DIR, logger.FILENAME)
        templog = []
        start = int(kwargs.get('start', 0))
        end = int(kwargs.get('end', 0))

        if regex:
            logger.debug('Filtering log using regex %s' % regex)
            reg = re.compile('u' + regex, flags=re.I)

        for line in open(logfile, 'r').readlines():
            temp_loglevel_and_time = None

            try:
                temp_loglevel_and_time = line.split('- ')
                loglvl = temp_loglevel_and_time[1].split(' :')[0].strip()
                tl_tread = line.split(' :: ')
                if loglvl is None:
                    msg = line.replace('\n', '')
                else:
                    msg = line.split(' : ')[1].replace('\n', '')
                thread = tl_tread[1].split(' : ')[0]
            except IndexError:
                # We assume this is a traceback
                tl = (len(templog) - 1)
                templog[tl]['msg'] += line.replace('\n', '')
                continue

            if len(line) > 1 and temp_loglevel_and_time is not None and loglvl in line:

                d = {
                    'time': temp_loglevel_and_time[0],
                    'loglevel': loglvl,
                    'msg': msg.replace('\n', ''),
                    'thread': thread
                }
                templog.append(d)

        if end > 0:
                logger.debug('Slicing the log from %s to %s' % (start, end))
                templog = templog[start:end]

        if sort:
            logger.debug('Sorting log based on %s' % sort)
            templog = sorted(templog, key=lambda k: k[sort])

        if search:
            logger.debug('Searching log values for %s' % search)
            tt = [d for d in templog for k, v in d.items() if search.lower() in v.lower()]

            if len(tt):
                templog = tt

        if regex:
            tt = []
            for l in templog:
                stringdict = ' '.join('{}{}'.format(k, v) for k, v in l.items())
                if reg.search(stringdict):
                    tt.append(l)

            if len(tt):
                templog = tt

        if order == 'desc':
            templog = templog[::-1]

        self.data = templog
        return templog

    def _getVersion(self, **kwargs):
        self.data = {
            'git_path': plexpy.CONFIG.GIT_PATH,
            'install_type': plexpy.INSTALL_TYPE,
            'current_version': plexpy.CURRENT_VERSION,
            'latest_version': plexpy.LATEST_VERSION,
            'commits_behind': plexpy.COMMITS_BEHIND,
        }
        self.result_type = 'success'

    def _checkGithub(self, **kwargs):
        versioncheck.checkGithub()
        self._getVersion()

    def _shutdown(self, **kwargs):
        plexpy.SIGNAL = 'shutdown'
        self.msg = 'Shutting down plexpy'
        self.result_type = 'success'

    def _restart(self, **kwargs):
        plexpy.SIGNAL = 'restart'
        self.msg = 'Restarting plexpy'
        self.result_type = 'success'

    def _update(self, **kwargs):
        plexpy.SIGNAL = 'update'
        self.msg = 'Updating plexpy'
        self.result_type = 'success'

    def _getHistory(self, user=None, user_id=None, rating_key='', parent_rating_key='', grandparent_rating_key='', start_date='', **kwargs):

        custom_where = []
        if user_id:
            custom_where = [['user_id', user_id]]
        elif user:
            custom_where = [['user', user]]
        if 'rating_key' in kwargs:
            rating_key = kwargs.get('rating_key', "")
            custom_where = [['rating_key', rating_key]]
        if 'parent_rating_key' in kwargs:
            rating_key = kwargs.get('parent_rating_key', "")
            custom_where = [['parent_rating_key', rating_key]]
        if 'grandparent_rating_key' in kwargs:
            rating_key = kwargs.get('grandparent_rating_key', "")
            custom_where = [['grandparent_rating_key', rating_key]]
        if 'start_date' in kwargs:
            start_date = kwargs.get('start_date', "")
            custom_where = [['strftime("%Y-%m-%d", datetime(date, "unixepoch", "localtime"))', start_date]]

        data_factory = datafactory.DataFactory()
        history = data_factory.get_datatables_history(kwargs=kwargs, custom_where=custom_where)

        self.data = history
        return self.data

    def _getSync(self, machine_id=None, user_id=None, **kwargs):

        pms_connect = pmsconnect.PmsConnect()
        server_id = pms_connect.get_server_identity()

        plex_tv = plextv.PlexTV()
        if not machine_id:
            result = plex_tv.get_synced_items(machine_id=server_id['machine_identifier'], user_id=user_id)
        else:
            result = plex_tv.get_synced_items(machine_id=machine_id, user_id=user_id)

        if result:
            self.data = result
            return result
        else:
            self.msg = 'Unable to retrieve sync data for user'
            logger.warn('Unable to retrieve sync data for user.')

    def _getMetadata(self, rating_key='', **kwargs):

        pms_connect = pmsconnect.PmsConnect()
        result = pms_connect.get_metadata(rating_key, 'dict')

        if result:
            self.data = result
            return result
        else:
            self.msg = 'Unable to retrive metadata %s' % rating_key
            logger.warn('Unable to retrieve data.')

    def _getSettings(self):
        interface_dir = os.path.join(plexpy.PROG_DIR, 'data/interfaces/')
        interface_list = [name for name in os.listdir(interface_dir) if
                          os.path.isdir(os.path.join(interface_dir, name))]

        config = {
            "http_host": plexpy.CONFIG.HTTP_HOST,
            "http_username": plexpy.CONFIG.HTTP_USERNAME,
            "http_port": plexpy.CONFIG.HTTP_PORT,
            "http_password": plexpy.CONFIG.HTTP_PASSWORD,
            "launch_browser": bool(plexpy.CONFIG.LAUNCH_BROWSER),
            "enable_https": bool(plexpy.CONFIG.ENABLE_HTTPS),
            "https_cert": plexpy.CONFIG.HTTPS_CERT,
            "https_key": plexpy.CONFIG.HTTPS_KEY,
            "api_enabled": plexpy.CONFIG.API_ENABLED,
            "api_key": plexpy.CONFIG.API_KEY,
            "update_db_interval": plexpy.CONFIG.UPDATE_DB_INTERVAL,
            "freeze_db": bool(plexpy.CONFIG.FREEZE_DB),
            "log_dir": plexpy.CONFIG.LOG_DIR,
            "cache_dir": plexpy.CONFIG.CACHE_DIR,
            "check_github": bool(plexpy.CONFIG.CHECK_GITHUB),
            "interface_list": interface_list,
            "cache_sizemb": plexpy.CONFIG.CACHE_SIZEMB,
            "pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER,
            "pms_ip": plexpy.CONFIG.PMS_IP,
            "pms_logs_folder": plexpy.CONFIG.PMS_LOGS_FOLDER,
            "pms_port": plexpy.CONFIG.PMS_PORT,
            "pms_token": plexpy.CONFIG.PMS_TOKEN,
            "pms_ssl": bool(plexpy.CONFIG.PMS_SSL),
            "pms_use_bif": bool(plexpy.CONFIG.PMS_USE_BIF),
            "pms_uuid": plexpy.CONFIG.PMS_UUID,
            "date_format": plexpy.CONFIG.DATE_FORMAT,
            "time_format": plexpy.CONFIG.TIME_FORMAT,
            "grouping_global_history": bool(plexpy.CONFIG.GROUPING_GLOBAL_HISTORY),
            "grouping_user_history": bool(plexpy.CONFIG.GROUPING_USER_HISTORY),
            "grouping_charts": bool(plexpy.CONFIG.GROUPING_CHARTS),
            "movie_notify_enable": bool(plexpy.CONFIG.MOVIE_NOTIFY_ENABLE),
            "tv_notify_enable": bool(plexpy.CONFIG.TV_NOTIFY_ENABLE),
            "music_notify_enable": bool(plexpy.CONFIG.MUSIC_NOTIFY_ENABLE),
            "tv_notify_on_start": bool(plexpy.CONFIG.TV_NOTIFY_ON_START),
            "movie_notify_on_start": bool(plexpy.CONFIG.MOVIE_NOTIFY_ON_START),
            "music_notify_on_start": bool(plexpy.CONFIG.MUSIC_NOTIFY_ON_START),
            "tv_notify_on_stop": bool(plexpy.CONFIG.TV_NOTIFY_ON_STOP),
            "movie_notify_on_stop": bool(plexpy.CONFIG.MOVIE_NOTIFY_ON_STOP),
            "music_notify_on_stop": bool(plexpy.CONFIG.MUSIC_NOTIFY_ON_STOP),
            "tv_notify_on_pause": bool(plexpy.CONFIG.TV_NOTIFY_ON_PAUSE),
            "movie_notify_on_pause": bool(plexpy.CONFIG.MOVIE_NOTIFY_ON_PAUSE),
            "music_notify_on_pause": bool(plexpy.CONFIG.MUSIC_NOTIFY_ON_PAUSE),
            "monitoring_interval": plexpy.CONFIG.MONITORING_INTERVAL,
            "refresh_users_interval": plexpy.CONFIG.REFRESH_USERS_INTERVAL,
            "refresh_users_on_startup": bool(plexpy.CONFIG.REFRESH_USERS_ON_STARTUP),
            "ip_logging_enable": bool(plexpy.CONFIG.IP_LOGGING_ENABLE),
            "movie_logging_enable": bool(plexpy.CONFIG.MOVIE_LOGGING_ENABLE),
            "tv_logging_enable": bool(plexpy.CONFIG.TV_LOGGING_ENABLE),
            "music_logging_enable": bool(plexpy.CONFIG.MUSIC_LOGGING_ENABLE),
            "logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL,
            "pms_is_remote": bool(plexpy.CONFIG.PMS_IS_REMOTE),
            "notify_watched_percent": plexpy.CONFIG.NOTIFY_WATCHED_PERCENT,
            "notify_on_start_subject_text": plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT,
            "notify_on_start_body_text": plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT,
            "notify_on_stop_subject_text": plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT,
            "notify_on_stop_body_text": plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT,
            "notify_on_pause_subject_text": plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT,
            "notify_on_pause_body_text": plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT,
            "notify_on_resume_subject_text": plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT,
            "notify_on_resume_body_text": plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT,
            "notify_on_buffer_subject_text": plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT,
            "notify_on_buffer_body_text": plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT,
            "notify_on_watched_subject_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT,
            "notify_on_watched_body_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT,
            "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
            "home_stats_type": bool(plexpy.CONFIG.HOME_STATS_TYPE),
            "home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
            "home_stats_cards": plexpy.CONFIG.HOME_STATS_CARDS,
            "home_library_cards": plexpy.CONFIG.HOME_LIBRARY_CARDS,
            "buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD,
            "buffer_wait": plexpy.CONFIG.BUFFER_WAIT
        }

        self.data = config
        return config

    def _getUserips(self, user_id=None, user=None, **kwargs):
        custom_where = []
        if user_id:
            custom_where = [['user_id', user_id]]
        elif user:
            custom_where = [['user', user]]

        user_data = users.Users()
        history = user_data.get_user_unique_ips(kwargs=kwargs,
                                                custom_where=custom_where)

        if history:
            self.data = history
            return history
        else:
            self.msg = 'Failed to find users ips'

    def _getPlayby(self, time_range='30', y_axis='plays', playtype='total_plays_per_month', **kwargs):

        graph = graphs.Graphs()
        if playtype == 'total_plays_per_month':
            result = graph.get_total_plays_per_month(y_axis=y_axis)

        elif playtype == 'total_plays_per_day':
            result = graph.get_total_plays_per_day(time_range=time_range, y_axis=y_axis)

        elif playtype == 'total_plays_per_hourofday':
            result = graph.get_total_plays_per_hourofday(time_range=time_range, y_axis=y_axis)

        elif playtype == 'total_plays_per_dayofweek':
            result = graph.get_total_plays_per_dayofweek(time_range=time_range, y_axis=y_axis)

        elif playtype == 'stream_type_by_top_10_users':
            result = graph.get_stream_type_by_top_10_users(time_range=time_range, y_axis=y_axis)

        elif playtype == 'stream_type_by_top_10_platforms':
            result = graph.get_stream_type_by_top_10_platforms(time_range=time_range, y_axis=y_axis)

        elif playtype == 'total_plays_by_stream_resolution':
            result = graph.get_total_plays_by_stream_resolution(time_range=time_range, y_axis=y_axis)

        elif playtype == 'total_plays_by_source_resolution':
            result = graph.get_total_plays_by_source_resolution(time_range=time_range, y_axis=y_axis)

        elif playtype == 'total_plays_per_stream_type':
            result = graph.get_total_plays_per_stream_type(time_range=time_range, y_axis=y_axis)

        elif playtype == 'total_plays_by_top_10_users':
            result = graph.get_total_plays_by_top_10_users(time_range=time_range, y_axis=y_axis)

        elif playtype == 'total_plays_by_top_10_platforms':
            result = graph.get_total_plays_by_top_10_platforms(time_range=time_range, y_axis=y_axis)

        if result:
            self.data = result
            return result
        else:
            logger.warn('Unable to retrieve %s from db' % playtype)
