# -*- coding: utf-8 -*-

#################################################################################################

import logging
import Queue
import threading
import os

import xbmc
import xbmcvfs
import xbmcaddon

import requests
from helper.utils import delete_folder
from helper import settings, stop, event, window, unzip, create_id
from jellyfin import Jellyfin
from jellyfin.core import api
from jellyfin.core.exceptions import HTTPException

#################################################################################################

LOG = logging.getLogger("JELLYFIN."+__name__)
LIMIT = min(int(settings('limitIndex') or 50), 50)
CACHE = xbmc.translatePath(os.path.join(xbmcaddon.Addon(id='plugin.video.jellyfin').getAddonInfo('profile').decode('utf-8'), 'jellyfin')).decode('utf-8')

#################################################################################################


def get_jellyfinserver_url(handler):

    if handler.startswith('/'):

        handler = handler[1:]
        LOG.warn("handler starts with /: %s", handler)

    return "{server}/emby/%s" % handler


def browse_info():
    return (
        "DateCreated,EpisodeCount,SeasonCount,Path,Genres,Studios,Taglines,MediaStreams,Overview,Etag,"
        "ProductionLocations,Width,Height,RecursiveItemCount,ChildCount"
        )


def _http(action, url, request={}, server_id=None):
    request.update({'url': url, 'type': action})
    
    return Jellyfin(server_id)['http/request'](request)


def _get(handler, params=None, server_id=None):
    return _http("GET", get_jellyfinserver_url(handler), {'params': params}, server_id)


def _post(handler, json=None, params=None, server_id=None):
    return _http("POST", get_jellyfinserver_url(handler), {'params': params, 'json': json}, server_id)


def _delete(handler, params=None, server_id=None):
    return _http("DELETE", get_jellyfinserver_url(handler), {'params': params}, server_id)


def validate_view(library_id, item_id):

    ''' This confirms a single item from the library matches the view it belongs to.
        Used to detect grouped libraries.
    '''
    try:
        result = _get("Users/{UserId}/Items", {
                    'ParentId': library_id,
                    'Recursive': True,
                    'Ids': item_id
                 })
    except Exception:
        return False

    return True if len(result['Items']) else False


def get_single_item(parent_id, media):
    return _get("Users/{UserId}/Items", {
        'ParentId': parent_id,
        'Recursive': True,
        'Limit': 1,
        'IncludeItemTypes': media
    })


def get_filtered_section(parent_id=None, media=None, limit=None, recursive=None, sort=None, sort_order=None,
                         filters=None, extra=None, server_id=None):

    ''' Get dynamic listings.
    '''
    params = {
        'ParentId': parent_id,
        'IncludeItemTypes': media,
        'IsMissing': False,
        'Recursive': recursive if recursive is not None else True,
        'Limit': limit,
        'SortBy': sort or "SortName",
        'SortOrder': sort_order or "Ascending",
        'ImageTypeLimit': 1,
        'IsVirtualUnaired': False,
        'Fields': browse_info()
    }
    if filters:
        if 'Boxsets' in filters:
            filters.remove('Boxsets')
            params['CollapseBoxSetItems'] = settings('groupedSets.bool')

        params['Filters'] = ','.join(filters)

    if settings('getCast.bool'):
        params['Fields'] += ",People"

    if media and 'Photo' in media:
        params['Fields'] += ",Width,Height"

    if extra is not None:
        params.update(extra)

    return _get("Users/{UserId}/Items", params, server_id)


def get_movies_by_boxset(boxset_id):

    for items in get_items(boxset_id, "Movie"):
        yield items


def get_episode_by_show(show_id):

    query = {
        'url': "Shows/%s/Episodes" % show_id,
        'params': {
            'EnableUserData': True, 
            'EnableImages': True, 
            'UserId': "{UserId}",
            'Fields': api.info()
        }
    }
    for items in _get_items(query):
        yield items


def get_episode_by_season(show_id, season_id):

    query = {
        'url': "Shows/%s/Episodes" % show_id,
        'params': {
            'SeasonId': season_id,
            'EnableUserData': True, 
            'EnableImages': True, 
            'UserId': "{UserId}",
            'Fields': api.info()
        }
    }
    for items in _get_items(query):
        yield items


def get_items(parent_id, item_type=None, basic=False, params=None):

    query = {
        'url': "Users/{UserId}/Items",
        'params': {
            'ParentId': parent_id,
            'IncludeItemTypes': item_type,
            'SortBy': "SortName",
            'SortOrder': "Ascending",
            'Fields': api.basic_info() if basic else api.info(),
            'CollapseBoxSetItems': False,
            'IsVirtualUnaired': False,
            'EnableTotalRecordCount': False,
            'LocationTypes': "FileSystem,Remote,Offline",
            'IsMissing': False,
            'Recursive': True
        }
    }
    if params:
        query['params'].update(params)

    for items in _get_items(query):
        yield items


def get_artists(parent_id=None, basic=False, params=None, server_id=None):

    query = {
        'url': "Artists",
        'params': {
            'UserId': "{UserId}",
            'ParentId': parent_id,
            'SortBy': "SortName",
            'SortOrder': "Ascending",
            'Fields': api.basic_info() if basic else api.music_info(),
            'CollapseBoxSetItems': False,
            'IsVirtualUnaired': False,
            'EnableTotalRecordCount': False,
            'LocationTypes': "FileSystem,Remote,Offline",
            'IsMissing': False,
            'Recursive': True
        }
    }

    if params:
        query['params'].update(params)

    for items in _get_items(query, server_id):
        yield items


def get_albums_by_artist(artist_id, basic=False):

    params = {
        'SortBy': "DateCreated",
        'ArtistIds': artist_id
    }
    for items in get_items(None, "MusicAlbum", basic, params):
        yield items


def get_songs_by_artist(artist_id, basic=False):

    params = {
        'SortBy': "DateCreated",
        'ArtistIds': artist_id
    }
    for items in get_items(None, "Audio", basic, params):
        yield items


@stop()
def _get_items(query, server_id=None):

    ''' query = {
            'url': string,
            'params': dict -- opt, include StartIndex to resume
        }
    '''
    items = {
        'Items': [],
        'TotalRecordCount': 0,
        'RestorePoint': {}
    }

    url = query['url']
    params = query.get('params', {})

    try:
        test_params = dict(params)
        test_params['Limit'] = 1
        test_params['EnableTotalRecordCount'] = True

        items['TotalRecordCount'] = _get(url, test_params, server_id=server_id)['TotalRecordCount']

    except Exception as error:
        LOG.error("Failed to retrieve the server response %s: %s params:%s", url, error, params)

    else:
        index = params.get('StartIndex', 0)
        total = items['TotalRecordCount']

        while index < total:

            params['StartIndex'] = index
            params['Limit'] = LIMIT
            result = _get(url, params, server_id=server_id) or {'Items': []}
            
            items['Items'].extend(result['Items'])
            items['RestorePoint'] = query
            yield items

            del items['Items'][:]
            index += LIMIT


class GetItemWorker(threading.Thread):

    is_done = False

    def __init__(self, server, queue, output):

        self.server = server
        self.queue = queue
        self.output = output
        threading.Thread.__init__(self)

    def run(self):
        with requests.Session() as s:
            while True:
                try:
                    item_ids = self.queue.get(timeout=1)
                except Queue.Empty:

                    self.is_done = True
                    LOG.info("--<[ q:download/%s ]", id(self))

                    return

                request = {
                    'type': "GET",
                    'handler': "Users/{UserId}/Items",
                    'params': {
                        'Ids': ','.join(str(x) for x in item_ids),
                        'Fields': api.info()
                    }
                }

                try:
                    result = self.server['http/request'](request, s)

                    for item in result['Items']:

                        if item['Type'] in self.output:
                            self.output[item['Type']].put(item)
                except HTTPException as error:
                    LOG.error("--[ http status: %s ]", error.status)

                    if error.status == 'ServerUnreachable':
                        self.is_done = True

                        break

                except Exception as error:
                    LOG.exception(error)

                self.queue.task_done()

                if window('jellyfin_should_stop.bool'):
                    break


class TheVoid(object):

    def __init__(self, method, data):

        ''' If you call get, this will block until response is received.
            This is used to communicate between entrypoints.
        '''
        if type(data) != dict:
            raise Exception("unexpected data format")

        data['VoidName'] = str(create_id())
        LOG.info("---[ contact MU-TH-UR 6000/%s ]", method)
        LOG.debug(data)

        event(method, data)
        self.method = method
        self.data = data

    def get(self):

        while True:

            response = window('jellyfin_%s.json' % self.data['VoidName'])

            if response != "":

                LOG.debug("--<[ nostromo/jellyfin_%s.json ]", self.data['VoidName'])
                window('jellyfin_%s' % self.data['VoidName'], clear=True)

                return response

            if window('jellyfin_should_stop.bool'):
                LOG.info("Abandon mission! A black hole just swallowed [ %s/%s ]", self.method, self.data['VoidName'])
                
                return

            xbmc.sleep(100)
            LOG.info("--[ void/%s ]", self.data['VoidName'])


def get_objects(src, filename):

    ''' Download objects dependency to temp cache folder.
    '''
    temp = CACHE
    restart = not xbmcvfs.exists(os.path.join(temp, "objects") + '/')
    path = os.path.join(temp, filename).encode('utf-8')

    if restart and (settings('appliedPatch') or "") == filename:

        LOG.warn("Something went wrong applying this patch %s previously.", filename)
        restart = False

    if not xbmcvfs.exists(path) or filename.startswith('DEV'):
        delete_folder(CACHE)

        LOG.info("From %s to %s", src, path.decode('utf-8'))
        try:
            response = requests.get(src, stream=True, verify=True)
            response.raise_for_status()
        except requests.exceptions.SSLError as error:

            LOG.error(error)
            response = requests.get(src, stream=True, verify=False)
        except Exception:
            raise

        dl = xbmcvfs.File(path, 'w')
        dl.write(response.content)
        dl.close()
        del response

        settings('appliedPatch', filename)

    unzip(path, temp, "objects")

    return restart