#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger

from .get_metadata import GetMetadataTask
from . import common, process_metadata, sections
from .. import utils, backgroundthread, variables as v, state
from .. import plex_functions as PF, itemtypes
from ..plex_db import PlexDB


if (v.PLATFORM != 'Microsoft UWP' and
        utils.settings('enablePlaylistSync') == 'true'):
    # Xbox cannot use watchdog, a dependency for PKC playlist features
    from .. import playlists
    PLAYLIST_SYNC_ENABLED = True
else:
    PLAYLIST_SYNC_ENABLED = False


LOG = getLogger('PLEX.library_sync.full_sync')


class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
    def __init__(self, repair, callback, show_dialog):
        """
        repair=True: force sync EVERY item
        """
        self.repair = repair
        self.callback = callback
        self.show_dialog = show_dialog
        self.queue = None
        self.process_thread = None
        self.last_sync = None
        self.plex_db = None
        self.plex_type = None
        self.processing_thread = None
        super(FullSync, self).__init__()

    def plex_update_watched(self, viewId, item_class, lastViewedAt=None,
                            updatedAt=None):
        """
        YET to implement

        Updates plex elements' view status ('watched' or 'unwatched') and
        also updates resume times.
        This is done by downloading one XML for ALL elements with viewId
        """
        if self.new_items_only is False:
            # Only do this once for fullsync: the first run where new items are
            # added to Kodi
            return
        xml = PF.GetAllPlexLeaves(viewId,
                                  lastViewedAt=lastViewedAt,
                                  updatedAt=updatedAt)
        # Return if there are no items in PMS reply - it's faster
        try:
            xml[0].attrib
        except (TypeError, AttributeError, IndexError):
            LOG.error('Error updating watch status. Could not get viewId: '
                      '%s of item_class %s with lastViewedAt: %s, updatedAt: '
                      '%s', viewId, item_class, lastViewedAt, updatedAt)
            return

        if item_class in ('Movies', 'TVShows'):
            self.update_kodi_video_library = True
        elif item_class == 'Music':
            self.update_kodi_music_library = True
        with getattr(itemtypes, item_class)() as itemtype:
            itemtype.updateUserdata(xml)

    def process_item(self, xml_item):
        """
        Processes a single library item
        """
        plex_id = int(xml_item['ratingKey'])
        if self.new_items_only:
            if self.plex_db.check_plexid(plex_id) is None:
                backgroundthread.BGThreader.addTask(
                    GetMetadataTask().setup(self.queue,
                                            plex_id,
                                            self.get_children))
        else:
            if self.plex_db.check_checksum(
                    int('%s%s' % (xml_item['ratingKey'],
                                  xml_item['updatedAt']))) is None:
                backgroundthread.BGThreader.addTask(
                    GetMetadataTask().setup(self.queue,
                                            plex_id,
                                            self.get_children))
            else:
                self.plex_db.update_last_sync(plex_id, self.last_sync)

    def process_delete(self):
        """
        Removes all the items that have NOT been updated (last_sync timestamp)
        is different
        """
        with self.context() as c:
            for plex_id in self.plex_db.plex_id_by_last_sync(self.plex_type,
                                                             self.last_sync):
                if self.isCanceled():
                    return
                c.remove(plex_id, plex_type=self.plex_type)

    @utils.log_time
    def process_kind(self):
        """
        """
        LOG.debug('Start processing %ss', self.plex_type)
        sections = (x for x in sections.SECTIONS
                    if x['plex_type'] == self.plex_type)
        for section in sections:
            LOG.debug('Processing library section %s', section)
            if self.isCanceled():
                return False
            if not self.install_sync_done:
                state.PATH_VERIFIED = False
            try:
                iterator = PF.SectionItems(
                    section['id'],
                    {'type': v.PLEX_TYPE_NUMBER_FROM_PLEX_TYPE[self.plex_type]})
                # Tell the processing thread about this new section
                queue_info = process_metadata.InitNewSection(
                    self.context,
                    utils.cast(int, iterator.get('totalSize', 0)),
                    utils.cast(unicode, iterator.get('librarySectionTitle')),
                    section['id'],
                    utils.cast(unicode, section['viewGroup']))
                self.queue.put(queue_info)
                for xml_item in iterator:
                    if self.isCanceled():
                        return False
                    self.process_item(xml_item)
            except RuntimeError:
                LOG.error('Could not entirely process section %s', section)
                continue
            self.queue.join()

        LOG.debug('Finished processing %ss', self.plex_type)
        return True

    def full_library_sync(self):
        """
        """
        kinds = [
            (v.PLEX_TYPE_MOVIE, itemtypes.Movie, False),
            (v.PLEX_TYPE_SHOW, itemtypes.Show, False),
            (v.PLEX_TYPE_SEASON, itemtypes.Season, False),
            (v.PLEX_TYPE_EPISODE, itemtypes.Episode, False)
        ]
        if state.ENABLE_MUSIC:
            kinds.extend(
                (v.PLEX_TYPE_ARTIST, itemtypes.Artist, False),
                (v.PLEX_TYPE_ALBUM, itemtypes.Album, True),
                (v.PLEX_TYPE_SONG, itemtypes.Song, False))
        for kind in kinds:
            # Setup our variables
            self.plex_type = kind[0]
            self.context = kind[1]
            self.get_children = kind[2]
            # Now do the heavy lifting
            with PlexDB() as self.plex_db:
                if self.isCanceled() or not self.process_kind():
                    return False
                if self.new_items_only:
                    # Delete movies that are not on Plex anymore - do this only once
                    self.process_delete()
        return True

    @utils.log_time
    def run(self):
        if self.isCanceled():
            return
        successful = False
        self.last_sync = utils.unix_timestamp()
        # Delete playlist and video node files from Kodi
        utils.delete_playlists()
        utils.delete_nodes()
        # Get latest Plex libraries and build playlist and video node files
        if not sections.sync_from_pms():
            return
        try:
            # Fire up our single processing thread
            self.queue = backgroundthread.Queue.Queue(maxsize=200)
            self.processing_thread = process_metadata.ProcessMetadata(
                self.queue, self.last_sync, self.show_dialog)
            self.processing_thread.start()

            # Actual syncing - do only new items first
            LOG.info('Running fullsync for **NEW** items with repair=%s',
                     self.repair)
            self.new_items_only = True
            # This will also update playstates and userratings!
            if not self.full_library_sync():
                return
            if self.isCanceled():
                return
            # This will NOT update playstates and userratings!
            LOG.info('Running fullsync for **CHANGED** items with repair=%s',
                     self.repair)
            self.new_items_only = False
            if not self.full_library_sync():
                return
            if self.isCanceled():
                return
            if PLAYLIST_SYNC_ENABLED and not playlists.full_sync():
                return
            successful = True
        except:
            utils.ERROR(txt='full_sync.py crashed', notify=True)
        finally:
            # Last element will kill the processing thread (if not already
            # done so, e.g. quitting Kodi)
            self.queue.put(None)
            # This will block until the processing thread exits
            LOG.debug('Waiting for processing thread to exit')
            self.processing_thread.join()
            if self.callback:
                self.callback(successful)
            LOG.info('Done full_sync')


def start(show_dialog, repair=False, callback=None):
    """
    """
    # backgroundthread.BGThreader.addTask(FullSync().setup(repair, callback))
    FullSync(repair, callback, show_dialog).start()