#!/usr/bin/env python
# -*- coding: utf-8 -*-
from threading import Lock

from .. import db, variables as v

PLEXDB_LOCK = Lock()

SUPPORTED_KODI_TYPES = (
    v.KODI_TYPE_MOVIE,
    v.KODI_TYPE_SHOW,
    v.KODI_TYPE_SEASON,
    v.KODI_TYPE_EPISODE,
    v.KODI_TYPE_ARTIST,
    v.KODI_TYPE_ALBUM,
    v.KODI_TYPE_SONG
)


class PlexDBBase(object):
    """
    Plex database methods used for all types of items.
    """
    def __init__(self, plexconn=None, lock=True, copy=False):
        # Allows us to use this class with a cursor instead of context mgr
        self.plexconn = plexconn
        self.cursor = self.plexconn.cursor() if self.plexconn else None
        self.lock = lock
        self.copy = copy

    def __enter__(self):
        if self.lock:
            PLEXDB_LOCK.acquire()
        self.plexconn = db.connect('plex-copy' if self.copy else 'plex')
        self.cursor = self.plexconn.cursor()
        return self

    def __exit__(self, e_typ, e_val, trcbak):
        try:
            if e_typ:
                # re-raise any exception
                return False
            self.plexconn.commit()
        finally:
            self.plexconn.close()
            if self.lock:
                PLEXDB_LOCK.release()

    def is_recorded(self, plex_id, plex_type):
        """
        FAST method to check whether a plex_id has already been recorded
        """
        self.cursor.execute('SELECT plex_id FROM %s WHERE plex_id = ?' % plex_type,
                            (plex_id, ))
        return self.cursor.fetchone() is not None

    def item_by_id(self, plex_id, plex_type=None):
        """
        Returns the item for plex_id or None.
        Supply with the correct plex_type to speed up lookup
        """
        answ = None
        if plex_type == v.PLEX_TYPE_MOVIE:
            answ = self.movie(plex_id)
        elif plex_type == v.PLEX_TYPE_EPISODE:
            answ = self.episode(plex_id)
        elif plex_type == v.PLEX_TYPE_SHOW:
            answ = self.show(plex_id)
        elif plex_type == v.PLEX_TYPE_SEASON:
            answ = self.season(plex_id)
        elif plex_type == v.PLEX_TYPE_SONG:
            answ = self.song(plex_id)
        elif plex_type == v.PLEX_TYPE_ALBUM:
            answ = self.album(plex_id)
        elif plex_type == v.PLEX_TYPE_ARTIST:
            answ = self.artist(plex_id)
        elif plex_type in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_PHOTO, v.PLEX_TYPE_PLAYLIST):
            # Will never be synched to Kodi
            pass
        elif plex_type is None:
            # SLOW - lookup plex_id in all our tables
            for kind in (v.PLEX_TYPE_MOVIE,
                         v.PLEX_TYPE_EPISODE,
                         v.PLEX_TYPE_SHOW,
                         v.PLEX_TYPE_SEASON,
                         'song',  # darn
                         v.PLEX_TYPE_ALBUM,
                         v.PLEX_TYPE_ARTIST):
                method = getattr(self, kind)
                answ = method(plex_id)
                if answ:
                    break
        return answ

    def item_by_kodi_id(self, kodi_id, kodi_type):
        """
        """
        if kodi_type not in SUPPORTED_KODI_TYPES:
            return
        self.cursor.execute('SELECT * from %s WHERE kodi_id = ? LIMIT 1'
                            % v.PLEX_TYPE_FROM_KODI_TYPE[kodi_type],
                            (kodi_id, ))
        method = getattr(self, 'entry_to_%s' % v.PLEX_TYPE_FROM_KODI_TYPE[kodi_type])
        return method(self.cursor.fetchone())

    def plex_id_by_last_sync(self, plex_type, last_sync, limit):
        """
        Returns an iterator for all items where the last_sync is NOT identical
        """
        query = '''
            SELECT plex_id FROM %s WHERE last_sync <> ? LIMIT %s
        ''' % (plex_type, limit)
        return (x[0] for x in self.cursor.execute(query, (last_sync, )))

    def checksum(self, plex_id, plex_type):
        """
        Returns the checksum for plex_id
        """
        self.cursor.execute('SELECT checksum FROM %s WHERE plex_id = ? LIMIT 1' % plex_type,
                            (plex_id, ))
        try:
            return self.cursor.fetchone()[0]
        except TypeError:
            pass

    def update_last_sync(self, plex_id, plex_type, last_sync):
        """
        Sets a new timestamp for plex_id
        """
        self.cursor.execute('UPDATE %s SET last_sync = ? WHERE plex_id = ?' % plex_type,
                            (last_sync, plex_id))

    def remove(self, plex_id, plex_type):
        """
        Removes the item from our Plex db
        """
        self.cursor.execute('DELETE FROM %s WHERE plex_id = ?' % plex_type, (plex_id, ))

    def every_plex_id(self, plex_type, offset, limit):
        """
        Returns an iterator for plex_type for every single plex_id
        Will start with records at DB position offset [int] and return limit
        [int] number of items
        """
        return (x[0] for x in
                self.cursor.execute('SELECT plex_id FROM %s LIMIT %s OFFSET %s'
                                    % (plex_type, limit, offset)))

    def missing_fanart(self, plex_type, offset, limit):
        """
        Returns an iterator for plex_type for all plex_id, where fanart_synced
        has not yet been set to 1
        Will start with records at DB position offset [int] and return limit
        [int] number of items
        """
        query = '''
            SELECT plex_id FROM %s WHERE fanart_synced = 0
            LIMIT %s OFFSET %s
        ''' % (plex_type, limit, offset)
        return (x[0] for x in self.cursor.execute(query))

    def missing_trailers(self, plex_type, offset, limit):
        """
        Returns an iterator for plex_type for all plex_id, where trailer_synced
        has not yet been set to 1
        Will start with records at DB position offset [int] and return limit
        [int] number of items
        """
        query = '''
            SELECT plex_id FROM %s WHERE trailer_synced = 0
            LIMIT %s OFFSET %s
        ''' % (plex_type, limit, offset)
        return (x[0] for x in self.cursor.execute(query))

    def set_fanart_synced(self, plex_id, plex_type):
        """
        Toggles fanart_synced to 1 for plex_id
        """
        self.cursor.execute('UPDATE %s SET fanart_synced = 1 WHERE plex_id = ?' % plex_type,
                            (plex_id, ))

    def set_trailer_synced(self, plex_id, plex_type):
        """
        Toggles fanart_synced to 1 for plex_id
        """
        self.cursor.execute('UPDATE %s SET trailer_synced = 1 WHERE plex_id = ?' % plex_type,
                            (plex_id, ))

    def plexid_by_sectionid(self, section_id, plex_type, limit):
        query = '''
            SELECT plex_id FROM %s WHERE section_id = ? LIMIT %s
        ''' % (plex_type, limit)
        return (x[0] for x in self.cursor.execute(query, (section_id, )))

    def kodiid_by_sectionid(self, section_id, plex_type):
        return (x[0] for x in
                self.cursor.execute('SELECT kodi_id FROM %s WHERE section_id = ?' % plex_type,
                                    (section_id, )))


def initialize():
        """
        Run once upon PKC startup to verify that plex db exists.
        """
        with PlexDBBase() as plexdb:
            plexdb.cursor.execute('''
                CREATE TABLE IF NOT EXISTS version(
                    idVersion TEXT PRIMARY KEY)
            ''')
            plexdb.cursor.execute('''
                INSERT OR REPLACE INTO version(idVersion)
                VALUES (?)
            ''', (v.ADDON_VERSION, ))
            plexdb.cursor.execute('''
                CREATE TABLE IF NOT EXISTS sections(
                    section_id INTEGER PRIMARY KEY,
                    section_name TEXT,
                    plex_type TEXT,
                    kodi_tagid INTEGER,
                    sync_to_kodi INTEGER,
                    last_sync INTEGER)
            ''')
            plexdb.cursor.execute('''
                CREATE TABLE IF NOT EXISTS movie(
                    plex_id INTEGER PRIMARY KEY,
                    checksum INTEGER UNIQUE,
                    section_id INTEGER,
                    kodi_id INTEGER,
                    kodi_fileid INTEGER,
                    kodi_pathid INTEGER,
                    fanart_synced INTEGER,
                    trailer_synced BOOLEAN,
                    last_sync INTEGER)
            ''')
            plexdb.cursor.execute('''
                CREATE TABLE IF NOT EXISTS show(
                    plex_id INTEGER PRIMARY KEY,
                    checksum INTEGER UNIQUE,
                    section_id INTEGER,
                    kodi_id INTEGER,
                    kodi_pathid INTEGER,
                    fanart_synced INTEGER,
                    last_sync INTEGER)
            ''')
            plexdb.cursor.execute('''
                CREATE TABLE IF NOT EXISTS season(
                    plex_id INTEGER PRIMARY KEY,
                    checksum INTEGER UNIQUE,
                    section_id INTEGER,
                    show_id INTEGER,
                    parent_id INTEGER,
                    kodi_id INTEGER,
                    fanart_synced INTEGER,
                    last_sync INTEGER)
            ''')
            plexdb.cursor.execute('''
                CREATE TABLE IF NOT EXISTS episode(
                    plex_id INTEGER PRIMARY KEY,
                    checksum INTEGER UNIQUE,
                    section_id INTEGER,
                    show_id INTEGER,
                    grandparent_id INTEGER,
                    season_id INTEGER,
                    parent_id INTEGER,
                    kodi_id INTEGER,
                    kodi_fileid INTEGER,
                    kodi_fileid_2 INTEGER,
                    kodi_pathid INTEGER,
                    fanart_synced INTEGER,
                    last_sync INTEGER)
            ''')
            plexdb.cursor.execute('''
                CREATE TABLE IF NOT EXISTS artist(
                    plex_id INTEGER PRIMARY KEY,
                    checksum INTEGER UNIQUE,
                    section_id INTEGER,
                    kodi_id INTEGER,
                    last_sync INTEGER)
            ''')
            plexdb.cursor.execute('''
                CREATE TABLE IF NOT EXISTS album(
                    plex_id INTEGER PRIMARY KEY,
                    checksum INTEGER UNIQUE,
                    section_id INTEGER,
                    artist_id INTEGER,
                    parent_id INTEGER,
                    kodi_id INTEGER,
                    last_sync INTEGER)
            ''')
            plexdb.cursor.execute('''
                CREATE TABLE IF NOT EXISTS track(
                    plex_id INTEGER PRIMARY KEY,
                    checksum INTEGER UNIQUE,
                    section_id INTEGER,
                    artist_id INTEGER,
                    grandparent_id INTEGER,
                    album_id INTEGER,
                    parent_id INTEGER,
                    kodi_id INTEGER,
                    kodi_pathid INTEGER,
                    last_sync INTEGER)
            ''')
            plexdb.cursor.execute('''
                CREATE TABLE IF NOT EXISTS playlists(
                    plex_id INTEGER PRIMARY KEY,
                    plex_name TEXT,
                    plex_updatedat INTEGER,
                    kodi_path TEXT,
                    kodi_type TEXT,
                    kodi_hash TEXT)
            ''')
            # DB indicees for faster lookups
            commands = (
                'CREATE INDEX IF NOT EXISTS ix_movie_1 ON movie (last_sync)',
                'CREATE UNIQUE INDEX IF NOT EXISTS ix_movie_2 ON movie (kodi_id)',
                'CREATE INDEX IF NOT EXISTS ix_show_1 ON show (last_sync)',
                'CREATE UNIQUE INDEX IF NOT EXISTS ix_show_2 ON show (kodi_id)',
                'CREATE INDEX IF NOT EXISTS ix_season_1 ON season (last_sync)',
                'CREATE UNIQUE INDEX IF NOT EXISTS ix_season_2 ON season (kodi_id)',
                'CREATE INDEX IF NOT EXISTS ix_episode_1 ON episode (last_sync)',
                'CREATE UNIQUE INDEX IF NOT EXISTS ix_episode_2 ON episode (kodi_id)',
                'CREATE INDEX IF NOT EXISTS ix_artist_1 ON artist (last_sync)',
                'CREATE UNIQUE INDEX IF NOT EXISTS ix_artist_2 ON artist (kodi_id)',
                'CREATE INDEX IF NOT EXISTS ix_album_1 ON album (last_sync)',
                'CREATE UNIQUE INDEX IF NOT EXISTS ix_album_2 ON album (kodi_id)',
                'CREATE INDEX IF NOT EXISTS ix_track_1 ON track (last_sync)',
                'CREATE UNIQUE INDEX IF NOT EXISTS ix_track_2 ON track (kodi_id)',
                'CREATE UNIQUE INDEX IF NOT EXISTS ix_playlists_2 ON playlists (kodi_path)',
                'CREATE INDEX IF NOT EXISTS ix_playlists_3 ON playlists (kodi_hash)',
            )
            for cmd in commands:
                plexdb.cursor.execute(cmd)


def wipe(table=None):
    """
    Completely resets the Plex database.
    If a table [unicode] name is provided, only that table will be dropped
    """
    with PlexDBBase() as plexdb:
        if table:
            tables = [table]
        else:
            plexdb.cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table'")
            tables = [i[0] for i in plexdb.cursor.fetchall()]
        for table in tables:
            plexdb.cursor.execute('DROP table IF EXISTS %s' % table)
