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

import datetime
import json
import os
import sqlite3
import sys
import re

from kodi_six import xbmc, xbmcvfs
from six import text_type

from . import jellyfin_db
from ..helper import translate, settings, window, dialog
from ..helper.utils import translate_path
from ..objects import obj
from ..helper import LazyLogger

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

LOG = LazyLogger(__name__)

ADDON_DATA = translate_path("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, db_file=None, commit_close=True):

        ''' file: jellyfin, texture, music, video, :memory: or path to file
        '''
        self.db_file = db_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 = translate_path(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 = translate_path("special://database/")
        types = {
            'video': "MyVideos",
            'music': "MyMusic",
            'texture': "Textures"
        }
        database = types[database]
        dirs, files = xbmcvfs.listdir(databases)
        target = {'db_file': '', 'version': 0}

        for db_file in reversed(files):
            if (db_file.startswith(database)
                    and not db_file.endswith('-wal')
                    and not db_file.endswith('-shm')
                    and not db_file.endswith('db-journal')):

                version_string = re.search('{}(.*).db'.format(database), db_file)
                version = int(version_string.group(1))

                if version > target['version']:
                    target['db_file'] = db_file
                    target['version'] = version

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

        return translate_path("special://database/%s" % target['db_file'])

    def _sql(self, db_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 db_file not in ('video', 'music', 'texture') or databases.get('database_set%s' % db_file):
            return self._get_database(databases[db_file], True)

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

        databases[db_file] = discovered
        self.discovered = True

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

        return databases[db_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 = translate_path('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 (3, 0) <= sys.version_info < (3, 6):
        LOG.error("Python versions 3.0-3.5 are NOT supported.")

    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)
    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', [])

    # Temporary cleanup from #494/#511, remove in a future version
    sync['Libraries'] = [lib_id for lib_id in sync['Libraries'] if ',' not in lib_id]

    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 (3, 0) <= sys.version_info < (3, 6):
        LOG.error("Python versions 3.0-3.5 are NOT supported.")

    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)
    except IOError:
        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