from modules.kodi_utils import trakt_db, database_connect
from modules.utils import chunks
# from modules.kodi_utils import logger

timeout = 20
SELECT = 'SELECT id FROM trakt_data'
DELETE = 'DELETE FROM trakt_data WHERE id = ?'
DELETE_LIKE = 'DELETE FROM trakt_data WHERE id LIKE "%s"'
WATCHED_INSERT = 'INSERT OR IGNORE INTO watched_status VALUES (?, ?, ?, ?, ?, ?)'
WATCHED_DELETE = 'DELETE FROM watched_status WHERE db_type = ?'
PROGRESS_INSERT = 'INSERT OR IGNORE INTO progress VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'
PROGRESS_DELETE = 'DELETE FROM progress WHERE db_type = ?'
BASE_DELETE = 'DELETE FROM %s'
TC_BASE_GET = 'SELECT data FROM trakt_data WHERE id = ?'
TC_BASE_SET = 'INSERT OR REPLACE INTO trakt_data (id, data) VALUES (?, ?)'
TC_BASE_DELETE = 'DELETE FROM trakt_data WHERE id = ?'

class TraktCache:
	batch_size = 1000
	def __init__(self):
		self._connect_database()
		self._set_PRAGMAS()

	def set_bulk_movie_watched(self, insert_list):
		self._delete(WATCHED_DELETE, ('movie',))
		for i in chunks(insert_list, self.batch_size):
			self._executemany(WATCHED_INSERT, i)

	def set_bulk_tvshow_watched(self, insert_list):
		self._delete(WATCHED_DELETE, ('episode',))
		for i in chunks(insert_list, self.batch_size):
			self._executemany(WATCHED_INSERT, i)

	def set_bulk_movie_progress(self, insert_list):
		self._delete(PROGRESS_DELETE, ('movie',))
		self._executemany(PROGRESS_INSERT, insert_list)

	def set_bulk_tvshow_progress(self, insert_list):
		self._delete(PROGRESS_DELETE, ('episode',))
		self._executemany(PROGRESS_INSERT, insert_list)

	def _executemany(self, command, insert_list):
		self.dbcur.executemany(command, insert_list)

	def _delete(self, command, args):
		self.dbcur.execute(command, args)
		self.dbcur.execute("""VACUUM""")

	def _connect_database(self):
		self.dbcon = database_connect(trakt_db, timeout=timeout, isolation_level=None)

	def _set_PRAGMAS(self):
		self.dbcur = self.dbcon.cursor()
		self.dbcur.execute("""PRAGMA synchronous = OFF""")
		self.dbcur.execute("""PRAGMA journal_mode = OFF""")
		self.dbcur.execute("""PRAGMA mmap_size = 268435456""")

def cache_trakt_object(function, string, url):
	# Ne jamais conserver un résultat Trakt invalide.
	# Un timeout ou une réponse vide pouvait auparavant être enregistré comme []/None,
	# ce qui laissait ensuite les watchlists vides jusqu'à une nouvelle purge.
	cache = TraktCache()
	dbcur = cache.dbcur
	dbcur.execute(TC_BASE_GET, (string,))
	cached_data = dbcur.fetchone()
	if cached_data:
		try:
			result = eval(cached_data[0])
			if result is not None: return result
		except Exception:
			pass
		try: dbcur.execute(TC_BASE_DELETE, (string,))
		except Exception: pass
	result = function(url)
	if result is None: return None
	dbcur.execute(TC_BASE_SET, (string, repr(result)))
	return result

def reset_activity(latest_activities):
	string = 'trakt_get_activity'
	cached_data = None
	try:
		dbcur = TraktCache().dbcur
		dbcur.execute(TC_BASE_GET, (string,))
		cached_data = dbcur.fetchone()
		if cached_data: cached_data = eval(cached_data[0])
		else: cached_data = default_activities()
		dbcur.execute(TC_BASE_SET, (string, repr(latest_activities)))
	except: pass
	return cached_data

def clear_trakt_hidden_data(list_type):
	string = 'trakt_hidden_items_%s' % list_type
	try:
		dbcur = TraktCache().dbcur
		dbcur.execute(DELETE, (string,))
	except: pass

def clear_trakt_collection_watchlist_data(list_type, media_type):
	if media_type == 'movies': media_type = 'movie'
	if media_type in ('tvshows', 'shows'): media_type = 'tvshow'
	string = 'trakt_%s_%s' % (list_type, media_type)
	try:
		dbcur = TraktCache().dbcur
		dbcur.execute(DELETE, (string,))
	except: pass

def clear_trakt_list_contents_data(list_type):
	string = 'trakt_list_contents_' + list_type + '_%'
	try:
		dbcur = TraktCache().dbcur
		dbcur.execute(DELETE_LIKE % string)
	except: pass

def clear_trakt_list_data(list_type):
	string = 'trakt_%s' % list_type
	try:
		dbcur = TraktCache().dbcur
		dbcur.execute(DELETE, (string,))
	except: pass

def clear_trakt_calendar():
	try:
		dbcur = TraktCache().dbcur
		dbcur.execute(DELETE_LIKE % 'trakt_get_my_calendar_%')
	except: return

def clear_trakt_recommendations(media_type):
	string = 'trakt_recommendations_%s' % (media_type)
	try:
		dbcur = TraktCache().dbcur
		dbcur.execute(DELETE, (string,))
	except: pass

def trakt_indicator_rows():
	try:
		dbcur = TraktCache().dbcur
		total = 0
		for table in ('watched_status', 'progress'):
			try:
				dbcur.execute('SELECT COUNT(*) FROM %s' % table)
				row = dbcur.fetchone()
				total += int((row or [0])[0] or 0)
			except Exception:
				pass
		return total
	except Exception:
		return 0


def trakt_indicators_empty():
	return trakt_indicator_rows() <= 0


def _rebuild_trakt_cache_after_clear(reason='manual', force_full_indicators=False):
	# La reconstruction complète de Trakt est coûteuse, donc on l'évite quand
	# les indicateurs watched/progress existent déjà. Par contre, après une
	# restauration ou une reconnexion, la base Trakt peut être vide : dans ce cas
	# il faut reconstruire les indicateurs, sinon les menus restent vides.
	try:
		from modules import kodi_utils
		prop = 'alkoflix_trakt_cache_rebuild_running'
		mode = 'full' if force_full_indicators else 'light'
		if kodi_utils.get_property(prop) == 'true':
			kodi_utils.logger('trakt cache rebuild', 'SKIPPED | already running | reason=%s | mode=%s' % (reason or 'manual', mode))
			return True
		kodi_utils.set_property(prop, 'true')
		kodi_utils.logger('trakt cache rebuild', 'START | reason=%s | mode=%s' % (reason or 'manual', mode))
		try:
			if force_full_indicators:
				from indexers.trakt_api import trakt_sync_activities
				status = trakt_sync_activities(force_update=True)
				kodi_utils.logger('trakt cache rebuild', 'FINISHED | reason=%s | status=%s | success=%s | mode=full' % (reason or 'manual', status, status not in ('failed', 'no account')))
				return status not in ('failed', 'no account')
			from indexers.trakt_api import trakt_get_activity
			latest = trakt_get_activity()
			# Important : en mode léger, on ne met plus à jour le pointeur d'activité ici.
			# Sinon alkoFlix peut croire que Trakt est synchronisé alors que les tables
			# watched/progress sont vides après une restauration.
			status = 'activity checked' if latest else 'activity skipped'
			success = bool(latest)
		except Exception as exc:
			status = 'activity failed: %s' % type(exc).__name__
			success = False
		kodi_utils.logger('trakt cache rebuild', 'FINISHED | reason=%s | status=%s | success=%s | mode=light' % (reason or 'manual', status, success))
		return success
	except Exception as exc:
		try:
			from modules import kodi_utils
			kodi_utils.logger('trakt cache rebuild', 'FAILED | reason=%s | %s: %s' % (reason or 'manual', type(exc).__name__, exc))
		except Exception: pass
		return False
	finally:
		try:
			from modules import kodi_utils
			kodi_utils.clear_property('alkoflix_trakt_cache_rebuild_running')
		except Exception: pass


def _start_trakt_cache_rebuild_after_clear(reason='manual', force_full_indicators=False):
	try:
		from threading import Thread
		Thread(target=_rebuild_trakt_cache_after_clear, args=(reason, force_full_indicators), name='alkoflix-trakt-cache-rebuild').start()
		return True
	except Exception:
		return _rebuild_trakt_cache_after_clear(reason=reason, force_full_indicators=force_full_indicators)


def clear_all_trakt_cache_data(refresh=True, reason='manual', clear_indicators=False):
	try:
		dbcur = TraktCache().dbcur
		indicator_rows = trakt_indicator_rows()
		# Le nettoyage manuel vise surtout les listes Trakt et les réponses API.
		# On conserve par défaut watched_status/progress afin d'éviter une reconstruction
		# très coûteuse qui ralentissait les menus après une purge.
		dbcur.execute(BASE_DELETE % 'trakt_data')
		if clear_indicators:
			for table in ('progress', 'watched_status'):
				dbcur.execute(BASE_DELETE % table)
			indicator_rows = 0
		try: dbcur.execute('PRAGMA optimize')
		except Exception: pass
		if not refresh: return True
		# Si les indicateurs sont absents, on reconstruit réellement Trakt en arrière-plan.
		# C'est le cas critique après restauration de sauvegarde ou reconnexion du compte.
		return _start_trakt_cache_rebuild_after_clear(reason=reason, force_full_indicators=indicator_rows <= 0)
	except Exception:
		return False

def default_activities():
	return {
			'all': '2020-01-01T00:00:01.000Z',
			'movies':
				{
				'watched_at': '2020-01-01T00:00:01.000Z',
				'collected_at': '2020-01-01T00:00:01.000Z',
				'rated_at': '2020-01-01T00:00:01.000Z',
				'watchlisted_at': '2020-01-01T00:00:01.000Z',
				'favorited_at': '2020-01-01T00:00:01.000Z',
				'recommendations_at': '2020-01-01T00:00:01.000Z',
				'commented_at': '2020-01-01T00:00:01.000Z',
				'paused_at': '2020-01-01T00:00:01.000Z',
				'hidden_at': '2020-01-01T00:00:01.000Z'
				},
			'episodes':
				{
				'watched_at': '2020-01-01T00:00:01.000Z',
				'collected_at': '2020-01-01T00:00:01.000Z',
				'rated_at': '2020-01-01T00:00:01.000Z',
				'watchlisted_at': '2020-01-01T00:00:01.000Z',
				'commented_at': '2020-01-01T00:00:01.000Z',
				'paused_at': '2020-01-01T00:00:01.000Z'
				},
			'shows':
				{
				'rated_at': '2020-01-01T00:00:01.000Z',
				'watchlisted_at': '2020-01-01T00:00:01.000Z',
				'favorited_at': '2020-01-01T00:00:01.000Z',
				'recommendations_at': '2020-01-01T00:00:01.000Z',
				'commented_at': '2020-01-01T00:00:01.000Z',
				'hidden_at': '2020-01-01T00:00:01.000Z',
				'dropped_at': '2020-01-01T00:00:01.000Z'
				},
			'seasons':
				{
				'rated_at': '2020-01-01T00:00:01.000Z',
				'watchlisted_at': '2020-01-01T00:00:01.000Z',
				'commented_at': '2020-01-01T00:00:01.000Z',
				'hidden_at': '2020-01-01T00:00:01.000Z'
				},
			'comments':
				{
				'liked_at': '2020-01-01T00:00:01.000Z'
				},
			'lists':
				{
				'liked_at': '2020-01-01T00:00:01.000Z',
				'updated_at': '2020-01-01T00:00:01.000Z',
				'commented_at': '2020-01-01T00:00:01.000Z'
				},
			'watchlist':
				{
				'updated_at': '2020-01-01T00:00:01.000Z'
				},
			'recommendations':
				{
				'updated_at': '2020-01-01T00:00:01.000Z'
				},
			'account':
				{
				'settings_at': '2020-01-01T00:00:01.000Z',
				'followed_at': '2020-01-01T00:00:01.000Z',
				'following_at': '2020-01-01T00:00:01.000Z',
				'pending_at': '2020-01-01T00:00:01.000Z'
				}
			}

