# -*- coding: utf-8 -*-
# plugin.video.alkoflix
# resources/lib/modules/favourites_background_sync.py

import json
import time
from modules import kodi_utils

logger = kodi_utils.logger
get_setting = kodi_utils.get_setting
path_exists = kodi_utils.path_exists
open_file = kodi_utils.open_file
make_directorys = kodi_utils.make_directorys

QUEUE_FILE = kodi_utils.get_addoninfo('profile') + 'external_favourites_sync_queue.json'
MAX_QUEUE_ITEMS = 200


def _safe_text(value):
	if value in (None, 'None'): return ''
	try: return str(value).strip()
	except Exception: return ''


def _setting_enabled(setting_id):
	try: return str(get_setting(setting_id, 'false')).strip().lower() in ('true', '1', 'yes', 'on')
	except Exception: return False


def _normal_favourites_media_type(media_type):
	media_type = _safe_text(media_type)
	if media_type in ('tv', 'show', 'series', 'shows'): return 'tvshow'
	if media_type in ('movie', 'movies'): return 'movie'
	if media_type in ('season', 'seasons'): return 'season'
	if media_type in ('episode', 'episodes'): return 'episode'
	return media_type


def _base_favourites_type(media_type):
	media_type = _normal_favourites_media_type(media_type)
	return 'tvshow' if media_type in ('tvshow', 'tv') else 'movie' if media_type == 'movie' else media_type


def _int_id(value):
	try: return int(value)
	except Exception: return None


def _queue_parent_dir():
	profile_dir = kodi_utils.get_addoninfo('profile')
	if not path_exists(profile_dir): make_directorys(profile_dir)
	return profile_dir


def _read_queue():
	try:
		if not path_exists(QUEUE_FILE): return []
		file_obj = open_file(QUEUE_FILE, 'r')
		text = file_obj.read()
		file_obj.close()
		if isinstance(text, bytes): text = text.decode('utf-8', 'ignore')
		data = json.loads(text or '[]')
		return data if isinstance(data, list) else []
	except Exception as exc:
		logger('alkoFlix favourites background sync queue read failed', '%s: %s' % (type(exc).__name__, exc))
		return []


def _write_queue(queue):
	try:
		_queue_parent_dir()
		queue = list(queue or [])[-MAX_QUEUE_ITEMS:]
		file_obj = open_file(QUEUE_FILE, 'w')
		file_obj.write(json.dumps(queue, ensure_ascii=False, separators=(',', ':')))
		file_obj.close()
		return True
	except Exception as exc:
		logger('alkoFlix favourites background sync queue write failed', '%s: %s' % (type(exc).__name__, exc))
		return False


def _queue_key(item):
	if item.get('kind') == 'bulk_clear':
		return 'bulk|%s|%s' % (item.get('provider', ''), item.get('queued_at', ''))
	return '|'.join([
		_safe_text(item.get('provider')),
		_safe_text(item.get('media_type')),
		_safe_text(item.get('tmdb_id')),
		_safe_text(item.get('imdb_id')),
		_safe_text(item.get('tvdb_id')),
		_safe_text(item.get('season')),
		_safe_text(item.get('episode'))
	])


def _append_queue_items(new_items):
	if not new_items: return False
	queue = _read_queue()
	for new_item in new_items:
		new_key = _queue_key(new_item)
		if new_item.get('kind') == 'item':
			# Si l’utilisateur ajoute puis retire le même favori avant le flush,
			# on garde seulement la dernière intention. Moins d’appels API, même résultat final.
			queue = [i for i in queue if not (i.get('kind') == 'item' and _queue_key(i) == new_key)]
		queue.append(new_item)
	return _write_queue(queue)


def _ids_from_item(item):
	ids = {}
	for key in ('imdb', 'tmdb', 'tvdb'):
		value = item.get('%s_id' % key)
		if value in ('', 'None', None): continue
		try: ids[key] = value if key == 'imdb' else int(value)
		except Exception: ids[key] = value
	return ids


def _favourite_ids_payload(item, media_type):
	ids = _ids_from_item(item)
	if not ids.get('tmdb') and item.get('tmdb_id') not in ('', 'None', None):
		try: ids['tmdb'] = int(item.get('tmdb_id'))
		except Exception: pass
	if not ids: return None
	if media_type == 'movie': return {'movies': [{'ids': ids}]}
	if media_type == 'tvshow': return {'shows': [{'ids': ids}]}
	if media_type == 'season':
		try: season = int(item.get('season'))
		except Exception: return None
		return {'shows': [{'ids': ids, 'seasons': [{'number': season}]}]}
	if media_type == 'episode':
		try:
			season = int(item.get('season'))
			episode = int(item.get('episode'))
		except Exception: return None
		return {'shows': [{'ids': ids, 'seasons': [{'number': season, 'episodes': [{'number': episode}]}]}]}
	return None


def queue_external_favourite_sync(params, action):
	params = dict(params or {})
	media_type = _base_favourites_type(params.get('media_type'))
	tmdb_id = _safe_text(params.get('tmdb_id') or params.get('media_id'))
	if not media_type or not tmdb_id: return False
	action_name = 'add' if action else 'remove'
	queued_at = int(time.time())
	base_item = {
		'kind': 'item',
		'action': action_name,
		'media_type': media_type,
		'tmdb_id': tmdb_id,
		'imdb_id': _safe_text(params.get('imdb_id')),
		'tvdb_id': _safe_text(params.get('tvdb_id')),
		'season': _safe_text(params.get('season')),
		'episode': _safe_text(params.get('episode')),
		'queued_at': queued_at
	}
	items = []
	if _setting_enabled('favourites.sync_trakt'):
		# Les favoris natifs Trakt ne supportent que films et séries. Les favoris
		# alkoFlix plus fins (saisons/épisodes) restent locaux/alkoPastes.
		if media_type not in ('movie', 'tvshow'):
			logger('alkoFlix favourites background sync', 'Trakt | %s | SKIPPED_UNSUPPORTED_TYPE | type=%s | tmdb=%s' % (action_name, media_type, tmdb_id))
		elif get_setting('trakt_user', ''):
			item = dict(base_item)
			item['provider'] = 'trakt'
			items.append(item)
		else:
			logger('alkoFlix favourites background sync', 'Trakt | %s | SKIPPED_NO_ACCOUNT | tmdb=%s' % (action_name, tmdb_id))
	if _setting_enabled('favourites.sync_tmdb'):
		if get_setting('tmdb.token', '') and media_type in ('movie', 'tvshow'):
			item = dict(base_item)
			item['provider'] = 'tmdb'
			items.append(item)
		elif media_type in ('movie', 'tvshow'):
			logger('alkoFlix favourites background sync', 'TMDb | %s | SKIPPED_NO_ACCOUNT | tmdb=%s' % (action_name, tmdb_id))
	if _append_queue_items(items):
		logger('alkoFlix favourites background sync', 'QUEUED | %s | providers=%s | tmdb=%s' % (action_name, ','.join([i.get('provider') for i in items]), tmdb_id))
		return True
	return False


def queue_external_favourites_bulk_clear(clear_types, clear_payload=None):
	payload = clear_payload or {}
	items = []
	queued_at = int(time.time())
	if _setting_enabled('favourites.sync_trakt') and get_setting('trakt_user', '') and payload:
		items.append({'kind': 'bulk_clear', 'provider': 'trakt', 'clear_types': list(clear_types or []), 'payload': payload, 'queued_at': queued_at})
	if _setting_enabled('favourites.sync_tmdb') and get_setting('tmdb.token', '') and payload:
		items.append({'kind': 'bulk_clear', 'provider': 'tmdb', 'clear_types': list(clear_types or []), 'payload': payload, 'queued_at': queued_at})
	if _append_queue_items(items):
		logger('alkoFlix favourites background sync', 'QUEUED_BULK_CLEAR | providers=%s | types=%s' % (','.join([i.get('provider') for i in items]), len(clear_types or [])))
		return True
	return False


def _trakt_native_payload(item):
	media_type = _base_favourites_type(item.get('media_type'))
	if media_type not in ('movie', 'tvshow'): return None
	payload = _favourite_ids_payload(item, media_type)
	if not payload: return None
	# Protection : l’endpoint natif sync/favorites de Trakt ne reçoit que films/séries.
	if payload.get('shows'):
		payload['shows'] = [show for show in payload.get('shows') or [] if not show.get('seasons')]
		if not payload['shows']: payload.pop('shows', None)
	return payload if payload.get('movies') or payload.get('shows') else None


def _trakt_count(result, bucket):
	try:
		data = (result or {}).get(bucket) or {}
		return int(data.get('movies') or 0) + int(data.get('shows') or 0)
	except Exception:
		return 0


def _sync_trakt_item(item):
	try:
		if not get_setting('trakt_user', ''): return False
		from indexers import trakt_api
		payload = _trakt_native_payload(item)
		if not payload: return True
		action = item.get('action')
		path = 'sync/favorites' if action == 'add' else 'sync/favorites/remove'
		result = trakt_api.call_trakt(path, data=payload)
		if isinstance(result, dict) and (result.get('account_limit') or result.get('rate_limited') or result.get('status') == 420):
			logger('alkoFlix favourites background Trakt sync', '%s | ACCOUNT_LIMIT_OR_RATE_LIMIT | tmdb=%s' % (action, item.get('tmdb_id')))
			return True
		if not isinstance(result, dict):
			return False
		added = _trakt_count(result, 'added')
		deleted = _trakt_count(result, 'deleted')
		existing = _trakt_count(result, 'existing')
		not_found = _trakt_count(result, 'not_found')
		try:
			trakt_api._clear_trakt_favourites_account_limit()
			trakt_api.trakt_cache.clear_trakt_collection_watchlist_data('favorites', 'movie')
			trakt_api.trakt_cache.clear_trakt_collection_watchlist_data('favorites', 'tvshow')
			trakt_api.trakt_sync_activities()
		except Exception: pass
		logger('alkoFlix favourites background Trakt native sync', '%s | added=%s | existing=%s | deleted=%s | not_found=%s | tmdb=%s' % (action, added, existing, deleted, not_found, item.get('tmdb_id')))
		if action == 'add': return (added + existing) > 0
		# Pour un retrait, not_found est acceptable : l’état final voulu est déjà atteint.
		return (deleted + not_found) > 0 or True
	except Exception as exc:
		logger('alkoFlix favourites background Trakt sync failed', '%s: %s' % (type(exc).__name__, exc))
		return False


def _sync_tmdb_item(item):
	try:
		if not get_setting('tmdb.token', ''): return False
		from indexers import tmdb_api
		media_type = _base_favourites_type(item.get('media_type'))
		if media_type not in ('movie', 'tvshow'): return False
		tmdb_id = int(item.get('tmdb_id') or 0)
		if not tmdb_id: return False
		payload = {'media_type': 'tv' if media_type == 'tvshow' else 'movie', 'media_id': tmdb_id, 'favorite': item.get('action') == 'add'}
		success = bool(tmdb_api.add_to_watchlist_favorite(payload, 'favorite').get('success'))
		if success: tmdb_api.clear_tmdbl_cache()
		return success
	except Exception as exc:
		logger('alkoFlix favourites background TMDb sync failed', '%s: %s' % (type(exc).__name__, exc))
		return False


def _native_trakt_bulk_payload(payload):
	movies = payload.get('movies') or []
	shows = [show for show in (payload.get('shows') or []) if not show.get('seasons')]
	result = {}
	if movies: result['movies'] = movies
	if shows: result['shows'] = shows
	return result


def _sync_trakt_bulk_clear(item):
	try:
		if not get_setting('trakt_user', ''): return False
		payload = _native_trakt_bulk_payload(item.get('payload') or {})
		if not payload: return True
		from indexers import trakt_api
		result = trakt_api.call_trakt('sync/favorites/remove', data=payload)
		if isinstance(result, dict) and (result.get('account_limit') or result.get('rate_limited') or result.get('status') == 420):
			logger('alkoFlix favourites background Trakt bulk sync', 'ACCOUNT_LIMIT_OR_RATE_LIMIT')
			return True
		if not isinstance(result, dict): return False
		deleted = _trakt_count(result, 'deleted')
		not_found = _trakt_count(result, 'not_found')
		try:
			trakt_api.trakt_cache.clear_trakt_collection_watchlist_data('favorites', 'movie')
			trakt_api.trakt_cache.clear_trakt_collection_watchlist_data('favorites', 'tvshow')
			trakt_api.trakt_sync_activities()
		except Exception: pass
		logger('alkoFlix favourites background Trakt bulk native sync', 'deleted=%s | not_found=%s' % (deleted, not_found))
		return True
	except Exception as exc:
		logger('alkoFlix favourites background Trakt bulk sync failed', '%s: %s' % (type(exc).__name__, exc))
		return False


def _sync_tmdb_bulk_clear(item):
	try:
		if not get_setting('tmdb.token', ''): return False
		payload = item.get('payload') or {}
		movies = payload.get('movies') or []
		shows = [i for i in payload.get('shows') or [] if not i.get('seasons')]
		if not movies and not shows: return False
		from indexers import tmdb_api
		deleted, failed = 0, 0
		for data, media_type in ((i, 'movie') for i in movies):
			tmdb_id = (data.get('ids') or {}).get('tmdb')
			if not tmdb_id: continue
			try:
				if tmdb_api.add_to_watchlist_favorite({'media_type': media_type, 'media_id': tmdb_id, 'favorite': False}, 'favorite').get('success'): deleted += 1
				else: failed += 1
			except Exception: failed += 1
		for data in shows:
			tmdb_id = (data.get('ids') or {}).get('tmdb')
			if not tmdb_id: continue
			try:
				if tmdb_api.add_to_watchlist_favorite({'media_type': 'tv', 'media_id': tmdb_id, 'favorite': False}, 'favorite').get('success'): deleted += 1
				else: failed += 1
			except Exception: failed += 1
		try: tmdb_api.clear_tmdbl_cache()
		except Exception: pass
		logger('alkoFlix favourites background TMDb bulk sync', 'deleted=%s | failed=%s' % (deleted, failed))
		return deleted > 0 and failed == 0
	except Exception as exc:
		logger('alkoFlix favourites background TMDb bulk sync failed', '%s: %s' % (type(exc).__name__, exc))
		return False


def _flush_one(item):
	provider = item.get('provider')
	kind = item.get('kind')
	if kind == 'bulk_clear':
		if provider == 'trakt': return _sync_trakt_bulk_clear(item)
		if provider == 'tmdb': return _sync_tmdb_bulk_clear(item)
	else:
		if provider == 'trakt': return _sync_trakt_item(item)
		if provider == 'tmdb': return _sync_tmdb_item(item)
	return False


def flush_pending_external_favourites_sync(max_items=8):
	queue = _read_queue()
	if not queue: return {'success': True, 'processed': 0, 'remaining': 0}
	processed, failed = 0, []
	for item in queue[:int(max_items or 8)]:
		try:
			success = _flush_one(item)
		except Exception as exc:
			logger('alkoFlix favourites background sync failed', '%s: %s' % (type(exc).__name__, exc))
			success = False
		if success:
			processed += 1
			try: logger('alkoFlix favourites background sync', 'DONE | %s | %s | tmdb=%s' % (item.get('provider'), item.get('action') or item.get('kind'), item.get('tmdb_id', '')))
			except Exception: pass
		else:
			item['attempts'] = int(item.get('attempts') or 0) + 1
			item['last_attempt_at'] = int(time.time())
			# On conserve quelques essais, mais on évite une file bloquée éternellement.
			if item['attempts'] < 5: failed.append(item)
			else:
				logger('alkoFlix favourites background sync', 'DROPPED_AFTER_RETRIES | %s | %s | tmdb=%s' % (item.get('provider'), item.get('action') or item.get('kind'), item.get('tmdb_id', '')))
	remaining = failed + queue[int(max_items or 8):]
	_write_queue(remaining)
	return {'success': not failed, 'processed': processed, 'remaining': len(remaining)}
