# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
#################################################################################################

import datetime
import logging
import json
import os
import sqlite3

from kodi_six import xbmc, xbmcvfs
from six import text_type

from database import jellyfin_db
from helper import translate, settings, window, dialog
from objects import obj
from helper import LazyLogger

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

LOG = LazyLogger(__name__)

ADDON_DATA = xbmc.translatePath("special://profile/addon_data/plugin.video.jellyfin/")

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


class Database(object):

    ''' This should be called like a context.
        i.e. with Database('jellyfin') as db:
            db.cursor
            db.conn.commit()
    '''
    timeout = 120
    discovered = False
    discovered_file = None

    def __init__(self, file=None, commit_close=True):

        ''' file: jellyfin, texture, music, video, :memory: or path to file
        '''
        self.db_file = file or "video"
        self.commit_close = commit_close

    def __enter__(self):

        ''' Open the connection and return the Database class.
            This is to allow for the cursor, conn and others to be accessible.
        '''
        self.path = self._sql(self.db_file)
        self.conn = sqlite3.connect(self.path, timeout=self.timeout)
        self.cursor = self.conn.cursor()

        if self.db_file in ('video', 'music', 'texture', 'jellyfin'):
            self.conn.execute("PRAGMA journal_mode=WAL")  # to avoid writing conflict with kodi

        LOG.debug("--->[ database: %s ] %s", self.db_file, id(self.conn))

        if not window('jellyfin_db_check.bool') and self.db_file == 'jellyfin':

            window('jellyfin_db_check.bool', True)
            jellyfin_tables(self.cursor)
            self.conn.commit()

        # Migration for #162
        if self.db_file == 'music':
            query = self.conn.execute('SELECT * FROM path WHERE strPath LIKE "%/emby/%"')
            contents = query.fetchall()
            if contents:
                for item in contents:
                    new_path = item[1].replace('/emby/', '/')
                    self.conn.execute('UPDATE path SET strPath = "{}" WHERE idPath = "{}"'.format(new_path, item[0]))

        return self

    def _get_database(self, path, silent=False):

        path = xbmc.translatePath(path)

        if not silent:

            if not xbmcvfs.exists(path):
                raise Exception("Database: %s missing" % path)

            conn = sqlite3.connect(path)
            cursor = conn.cursor()
            cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
            tables = cursor.fetchall()
            conn.close()

            if not len(tables):
                raise Exception("Database: %s malformed?" % path)

        return path

    def _discover_database(self, database):

        ''' Use UpdateLibrary(video) to update the date modified
            on the database file used by Kodi.
        '''
        if database == 'video':

            xbmc.executebuiltin('UpdateLibrary(video)')
            xbmc.sleep(200)

        databases = xbmc.translatePath("special://database/")
        types = {
            'video': "MyVideos",
            'music': "MyMusic",
            'texture': "Textures"
        }
        database = types[database]
        dirs, files = xbmcvfs.listdir(databases)
        modified = {'file': None, 'time': 0}

        for file in reversed(files):

            if (file.startswith(database) and not file.endswith('-wal') and not file.endswith('-shm') and not file.endswith('db-journal')):

                st = xbmcvfs.Stat(databases + file)
                modified_int = st.st_mtime()
                LOG.debug("Database detected: %s time: %s", file, modified_int)

                if modified_int > modified['time']:

                    modified['time'] = modified_int
                    modified['file'] = file

        LOG.debug("Discovered database: %s", modified)
        self.discovered_file = modified['file']

        return xbmc.translatePath("special://database/%s" % modified['file'])

    def _sql(self, file):

        ''' Get the database path based on the file objects/obj_map.json
            Compatible check, in the event multiple db version are supported with the same Kodi version.
            Discover by file as a last resort.
        '''
        databases = obj.Objects().objects

        if file not in ('video', 'music', 'texture') or databases.get('database_set%s' % file):
            return self._get_database(databases[file], True)

        discovered = self._discover_database(file) if not databases.get('database_set%s' % file) else None

        try:
            loaded = self._get_database(databases[file]) if file in databases else file
        except Exception as error:
            LOG.exception(error)

            for i in range(1, 10):
                alt_file = "%s-%s" % (file, i)

                try:
                    loaded = self._get_database(databases[alt_file])

                    break
                except KeyError:  # No other db options
                    loaded = None

                    break
                except Exception as error:
                    LOG.exception(error)

        if discovered and discovered != loaded:

            databases[file] = discovered
            self.discovered = True
        else:
            databases[file] = loaded

        databases['database_set%s' % file] = True
        LOG.info("Database locked in: %s", databases[file])

        return databases[file]

    def __exit__(self, exc_type, exc_val, exc_tb):

        ''' Close the connection and cursor.
        '''
        changes = self.conn.total_changes

        if exc_type is not None:  # errors raised
            LOG.error("type: %s value: %s", exc_type, exc_val)

        if self.commit_close and changes:

            LOG.debug("[%s] %s rows updated.", self.db_file, changes)
            self.conn.commit()

        LOG.debug("---<[ database: %s ] %s", self.db_file, id(self.conn))
        self.cursor.close()
        self.conn.close()


def jellyfin_tables(cursor):

    ''' Create the tables for the jellyfin database.
        jellyfin, view, version
    '''
    cursor.execute(
        """CREATE TABLE IF NOT EXISTS jellyfin(
        jellyfin_id TEXT UNIQUE, media_folder TEXT, jellyfin_type TEXT, media_type TEXT,
        kodi_id INTEGER, kodi_fileid INTEGER, kodi_pathid INTEGER, parent_id INTEGER,
        checksum INTEGER, jellyfin_parent_id TEXT)""")
    cursor.execute(
        """CREATE TABLE IF NOT EXISTS view(
        view_id TEXT UNIQUE, view_name TEXT, media_type TEXT)""")
    cursor.execute("CREATE TABLE IF NOT EXISTS version(idVersion TEXT)")

    columns = cursor.execute("SELECT * FROM jellyfin")
    if 'jellyfin_parent_id' not in [description[0] for description in columns.description]:

        LOG.debug("Add missing column jellyfin_parent_id")
        cursor.execute("ALTER TABLE jellyfin ADD COLUMN jellyfin_parent_id 'TEXT'")


def reset():

    ''' Reset both the jellyfin database and the kodi database.
    '''
    from views import Views
    views = Views()

    if not dialog("yesno", "{jellyfin}", translate(33074)):
        return

    window('jellyfin_should_stop.bool', True)
    count = 10

    while window('jellyfin_sync.bool'):

        LOG.info("Sync is running...")
        count -= 1

        if not count:
            dialog("ok", "{jellyfin}", translate(33085))

            return

        if xbmc.Monitor().waitForAbort(1):
            return

    reset_kodi()
    reset_jellyfin()
    views.delete_playlists()
    views.delete_nodes()

    if dialog("yesno", "{jellyfin}", translate(33086)):
        reset_artwork()

    if dialog("yesno", "{jellyfin}", translate(33087)):

        xbmcvfs.delete(os.path.join(ADDON_DATA, "settings.xml"))
        xbmcvfs.delete(os.path.join(ADDON_DATA, "data.json"))
        LOG.info("[ reset settings ]")

    if xbmcvfs.exists(os.path.join(ADDON_DATA, "sync.json")):
        xbmcvfs.delete(os.path.join(ADDON_DATA, "sync.json"))

    settings('enableMusic.bool', False)
    settings('MinimumSetup', "")
    settings('MusicRescan.bool', False)
    settings('SyncInstallRunDone.bool', False)
    dialog("ok", "{jellyfin}", translate(33088))
    xbmc.executebuiltin('RestartApp')


def reset_kodi():

    with Database() as videodb:
        videodb.cursor.execute("SELECT tbl_name FROM sqlite_master WHERE type='table'")

        for table in videodb.cursor.fetchall():
            name = table[0]

            if name != 'version':
                videodb.cursor.execute("DELETE FROM " + name)

    if settings('enableMusic.bool') or dialog("yesno", "{jellyfin}", translate(33162)):

        with Database('music') as musicdb:
            musicdb.cursor.execute("SELECT tbl_name FROM sqlite_master WHERE type='table'")

            for table in musicdb.cursor.fetchall():
                name = table[0]

                if name != 'version':
                    musicdb.cursor.execute("DELETE FROM " + name)

    LOG.info("[ reset kodi ]")


def reset_jellyfin():

    with Database('jellyfin') as jellyfindb:
        jellyfindb.cursor.execute("SELECT tbl_name FROM sqlite_master WHERE type='table'")

        for table in jellyfindb.cursor.fetchall():
            name = table[0]

            if name not in ('version', 'view'):
                jellyfindb.cursor.execute("DELETE FROM " + name)

            jellyfindb.cursor.execute("DROP table IF EXISTS jellyfin")
            jellyfindb.cursor.execute("DROP table IF EXISTS view")
            jellyfindb.cursor.execute("DROP table IF EXISTS version")

    LOG.info("[ reset jellyfin ]")


def reset_artwork():

    ''' Remove all existing texture.
    '''
    thumbnails = xbmc.translatePath('special://thumbnails/')

    if xbmcvfs.exists(thumbnails):
        dirs, ignore = xbmcvfs.listdir(thumbnails)

        for directory in dirs:
            ignore, thumbs = xbmcvfs.listdir(os.path.join(thumbnails, directory))

            for thumb in thumbs:
                LOG.debug("DELETE thumbnail %s", thumb)
                xbmcvfs.delete(os.path.join(thumbnails, directory, thumb))

    with Database('texture') as texdb:
        texdb.cursor.execute("SELECT tbl_name FROM sqlite_master WHERE type='table'")

        for table in texdb.cursor.fetchall():
            name = table[0]

            if name != 'version':
                texdb.cursor.execute("DELETE FROM " + name)

    LOG.info("[ reset artwork ]")


def get_sync():

    if not xbmcvfs.exists(ADDON_DATA):
        xbmcvfs.mkdirs(ADDON_DATA)

    try:
        with open(os.path.join(ADDON_DATA, 'sync.json'), 'rb') as infile:
            sync = json.load(infile, encoding='utf-8')
    except Exception:
        sync = {}

    sync['Libraries'] = sync.get('Libraries', [])
    sync['RestorePoint'] = sync.get('RestorePoint', {})
    sync['Whitelist'] = list(set(sync.get('Whitelist', [])))
    sync['SortedViews'] = sync.get('SortedViews', [])

    return sync


def save_sync(sync):

    if not xbmcvfs.exists(ADDON_DATA):
        xbmcvfs.mkdirs(ADDON_DATA)

    sync['Date'] = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')

    with open(os.path.join(ADDON_DATA, 'sync.json'), 'wb') as outfile:
        data = json.dumps(sync, sort_keys=True, indent=4, ensure_ascii=False)
        if isinstance(data, text_type):
            data = data.encode('utf-8')
        outfile.write(data)


def get_credentials():

    if not xbmcvfs.exists(ADDON_DATA):
        xbmcvfs.mkdirs(ADDON_DATA)

    try:
        with open(os.path.join(ADDON_DATA, 'data.json'), 'rb') as infile:
            credentials = json.load(infile, encoding='utf8')
    except Exception:

        try:
            with open(os.path.join(ADDON_DATA, 'data.txt'), 'rb') as infile:
                credentials = json.load(infile, encoding='utf-8')
                save_credentials(credentials)

            xbmcvfs.delete(os.path.join(ADDON_DATA, 'data.txt'))
        except Exception:
            credentials = {}

    credentials['Servers'] = credentials.get('Servers', [])

    # Migration for #145
    # TODO: CLEANUP for 1.0.0 release
    for server in credentials['Servers']:
        # Functionality removed in #60
        if 'RemoteAddress' in server:
            del server['RemoteAddress']
        if 'ManualAddress' in server:
            server['address'] = server['ManualAddress']
            del server['ManualAddress']
            # If manual is present, local should always be here, but better to be safe
            if 'LocalAddress' in server:
                del server['LocalAddress']
        elif 'LocalAddress' in server:
            server['address'] = server['LocalAddress']
            del server['LocalAddress']
        if 'LastConnectionMode' in server:
            del server['LastConnectionMode']

    return credentials


def save_credentials(credentials):
    credentials = credentials or {}

    if not xbmcvfs.exists(ADDON_DATA):
        xbmcvfs.mkdirs(ADDON_DATA)
    try:
        with open(os.path.join(ADDON_DATA, 'data.json'), 'wb') as outfile:
            data = json.dumps(credentials, sort_keys=True, indent=4, ensure_ascii=False)
            if isinstance(data, text_type):
                data = data.encode('utf-8')
            outfile.write(data)
    except Exception:
        LOG.exception("Failed to save credentials:")


def get_item(kodi_id, media):

    ''' Get jellyfin item based on kodi id and media.
    '''
    with Database('jellyfin') as jellyfindb:
        item = jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_full_item_by_kodi_id(kodi_id, media)

        if not item:
            LOG.debug("Not an jellyfin item")

            return

    return item