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

from threading import Thread
from caches.main_cache import MainCache, timedelta
from modules import kodi_utils, settings

# Cache léger des notes utilisateur. Il évite de solliciter Trakt/MDBList/TMDb
# à chaque construction d'une liste Kodi, tout en restant rafraîchi après une
# modification de note depuis le menu contextuel.
CACHE_PREFIX = 'alkoflix_user_ratings_'
CACHE_DURATION = timedelta(hours=12)
PROVIDER_NAMES = ('Trakt', 'MDBList', 'TMDb', 'Aucun')
LOG_HEADING = 'alkoFlix UserRatings'


def _log(message):
	try: kodi_utils.logger(LOG_HEADING, message)
	except: pass


def _empty_index():
	return {'movie': {}, 'tvshow': {}, 'season': {}, 'episode': {}}


def _valid(value):
	return not value in ('', 'None', None, 0, '0')


def _normalize_rating(value):
	try:
		value = int(float(value) + 0.5)
	except:
		return None
	if value < 1: value = 1
	elif value > 10: value = 10
	return value


def _index_count(index):
	try: return sum(len((index or {}).get(key) or {}) for key in ('movie', 'tvshow', 'season', 'episode'))
	except: return 0


def _index_counts(index):
	index = index or {}
	counts = {}
	for key in ('movie', 'tvshow', 'season', 'episode'):
		try: counts[key] = len(index.get(key) or {})
		except: counts[key] = 0
	counts['total'] = sum(counts.values())
	return counts


def _counts_log(index):
	counts = _index_counts(index)
	return 'movies=%s | shows=%s | seasons=%s | episodes=%s | total=%s' % (
		counts.get('movie', 0), counts.get('tvshow', 0), counts.get('season', 0), counts.get('episode', 0), counts.get('total', 0)
	)


def _provider_label(provider):
	try: return PROVIDER_NAMES[provider]
	except: return 'alkoFlix'


def _id_keys(media_type, ids=None, tmdb_id=None, imdb_id=None, tvdb_id=None, season=None, episode=None):
	ids = ids or {}
	values = {
		'tmdb': ids.get('tmdb', tmdb_id),
		'imdb': ids.get('imdb', imdb_id),
		'tvdb': ids.get('tvdb', tvdb_id)
	}
	keys = []
	for key, value in values.items():
		if not _valid(value): continue
		base = '%s:%s' % (key, value)
		if media_type == 'episode':
			if not (_valid(season) and _valid(episode)): continue
			base = '%s:s%s:e%s' % (base, int(season), int(episode))
		elif media_type == 'season':
			if not _valid(season): continue
			base = '%s:s%s' % (base, int(season))
		keys.append(base)
	return keys


def _add_rating(index, media_type, ids=None, rating=None, season=None, episode=None):
	rating = _normalize_rating(rating)
	if rating is None: return
	for key in _id_keys(media_type, ids=ids, season=season, episode=episode):
		index[media_type][key] = rating


def _build_trakt_index():
	from indexers import trakt_api
	index = _empty_index()
	_log('Build Starting | Provider: Trakt')
	for trakt_type, media_type, key_name in (
		('movies', 'movie', 'movie'),
		('shows', 'tvshow', 'show'),
		('seasons', 'season', 'season'),
		('episodes', 'episode', 'episode')
	):
		data = trakt_api.trakt_fetch_all_pages({'path': 'sync/ratings/%s', 'path_insert': trakt_type, 'params': {'limit': 250}, 'with_auth': True}) or []
		before = len(index.get(media_type) or {})
		skipped = 0
		for item in data:
			try:
				rating = item.get('rating')
				if media_type == 'movie':
					media = item.get('movie') or {}
					_add_rating(index, 'movie', media.get('ids'), rating)
				elif media_type == 'tvshow':
					media = item.get('show') or {}
					_add_rating(index, 'tvshow', media.get('ids'), rating)
				elif media_type == 'season':
					season = item.get('season') or {}
					show = item.get('show') or season.get('show') or {}
					_add_rating(index, 'season', show.get('ids'), rating, season=season.get('number', item.get('number')))
				elif media_type == 'episode':
					episode = item.get('episode') or {}
					show = item.get('show') or episode.get('show') or {}
					_add_rating(index, 'episode', show.get('ids'), rating, season=episode.get('season'), episode=episode.get('number'))
			except:
				skipped += 1
		added = len(index.get(media_type) or {}) - before
		_log('Build Detail | Provider: Trakt | Type: %s | fetched=%s | indexed=%s | skipped=%s' % (media_type, len(data), added, skipped))
	_log('Build Finished | Provider: Trakt | %s' % _counts_log(index))
	return index


def _build_mdbl_index():
	from indexers import mdblist_api
	index = _empty_index()
	_log('Build Starting | Provider: MDBList')
	data = mdblist_api.mdbl_user_ratings() or {}
	for source_key, media_type, media_key in (
		('movies', 'movie', 'movie'),
		('shows', 'tvshow', 'show'),
		('seasons', 'season', 'season'),
		('episodes', 'episode', 'episode')
	):
		items = data.get(source_key, []) or []
		before = len(index.get(media_type) or {})
		skipped = 0
		for item in items:
			try:
				if media_type == 'movie':
					media = item.get('movie') or {}
					_add_rating(index, 'movie', media.get('ids'), item.get('rating'))
				elif media_type == 'tvshow':
					media = item.get('show') or {}
					_add_rating(index, 'tvshow', media.get('ids'), item.get('rating'))
				elif media_type == 'season':
					season = item.get('season') or {}
					show = season.get('show') or item.get('show') or {}
					_add_rating(index, 'season', show.get('ids'), item.get('rating'), season=season.get('number', item.get('number')))
				elif media_type == 'episode':
					episode = item.get('episode') or {}
					show = episode.get('show') or item.get('show') or {}
					_add_rating(index, 'episode', show.get('ids'), item.get('rating'), season=episode.get('season'), episode=episode.get('number'))
			except:
				skipped += 1
		added = len(index.get(media_type) or {}) - before
		_log('Build Detail | Provider: MDBList | Type: %s | fetched=%s | indexed=%s | skipped=%s' % (media_type, len(items), added, skipped))
	_log('Build Finished | Provider: MDBList | %s' % _counts_log(index))
	return index


def _build_tmdb_index():
	from indexers import tmdb_api
	index = _empty_index()
	_log('Build Starting | Provider: TMDb')
	for tmdb_type, media_type in (('movie', 'movie'), ('tvshow', 'tvshow'), ('episode', 'episode')):
		items = tmdb_api.tmdb_rated_all(tmdb_type) or []
		before = len(index.get(media_type) or {})
		skipped = 0
		for item in items:
			try:
				if media_type == 'movie':
					_add_rating(index, 'movie', {'tmdb': item.get('id')}, item.get('rating'))
				elif media_type == 'tvshow':
					_add_rating(index, 'tvshow', {'tmdb': item.get('id')}, item.get('rating'))
				elif media_type == 'episode':
					show_id = item.get('show_id') or item.get('series_id') or item.get('tv_id')
					_add_rating(index, 'episode', {'tmdb': show_id}, item.get('rating'), season=item.get('season_number'), episode=item.get('episode_number'))
			except:
				skipped += 1
		added = len(index.get(media_type) or {}) - before
		_log('Build Detail | Provider: TMDb | Type: %s | fetched=%s | indexed=%s | skipped=%s' % (media_type, len(items), added, skipped))
	_log('Build Detail | Provider: TMDb | Type: season | fetched=0 | indexed=0 | skipped=0 | unsupported=true')
	_log('Build Finished | Provider: TMDb | %s' % _counts_log(index))
	return index

def _provider_authorized(provider):
	if provider == 0: return bool(kodi_utils.get_setting('trakt_user', ''))
	if provider == 1: return bool(kodi_utils.get_setting('mdblist_user', '') or kodi_utils.get_setting('mdblist.access_token', '') or kodi_utils.get_setting('mdblist.token', ''))
	if provider == 2: return bool(kodi_utils.get_setting('tmdb.token', ''))
	return False


def _build_provider_index(provider):
	if provider == 0: return _build_trakt_index()
	if provider == 1: return _build_mdbl_index()
	if provider == 2: return _build_tmdb_index()
	return _empty_index()


def get_active_ratings_index():
	provider = max(0, min(settings.ratings_provider(), 3))
	provider_name = _provider_label(provider)
	if provider == 3:
		return _empty_index()
	if not _provider_authorized(provider):
		_log('Index Aborted | Provider: %s | Reason: unauthorized' % provider_name)
		return _empty_index()
	cache_key = '%s%s' % (CACHE_PREFIX, provider)
	cache = MainCache().get(cache_key)
	if cache is not None: return cache
	try:
		_log('Index Cache Miss | Provider: %s | Rebuilding index' % provider_name)
		index = _build_provider_index(provider)
	except Exception as e:
		_log('Index Error | Provider: %s | %s: %s' % (provider_name, type(e).__name__, e))
		index = _empty_index()
	try:
		MainCache().set(cache_key, index, expiration=CACHE_DURATION)
		_log('Index Cache Saved | Provider: %s | %s' % (provider_name, _counts_log(index)))
	except Exception as e:
		_log('Index Cache Save Error | Provider: %s | %s: %s' % (provider_name, type(e).__name__, e))
	return index


def sync_existing_ratings(provider=None):
	provider = max(0, min(settings.ratings_provider(), 3)) if provider is None else max(0, min(int(provider), 3))
	provider_name = _provider_label(provider)
	cache_key = '%s%s' % (CACHE_PREFIX, provider)
	_log('Sync Starting | Provider: %s' % provider_name)
	if provider == 3:
		_log('Sync Aborted | Provider: %s | Reason: disabled' % provider_name)
		return {'success': False, 'provider': provider_name, 'count': 0, 'error': 'disabled'}
	if not _provider_authorized(provider):
		_log('Sync Aborted | Provider: %s | Reason: unauthorized' % provider_name)
		return {'success': False, 'provider': provider_name, 'count': 0, 'error': 'unauthorized'}
	try:
		MainCache().delete(cache_key)
		_log('Sync Cache Cleared | Provider: %s | Key: %s' % (provider_name, cache_key))
	except Exception as e:
		_log('Sync Cache Clear Error | Provider: %s | %s: %s' % (provider_name, type(e).__name__, e))
	try:
		index = _build_provider_index(provider)
	except Exception as e:
		_log('Sync Error | Provider: %s | %s: %s' % (provider_name, type(e).__name__, e))
		return {'success': False, 'provider': provider_name, 'count': 0, 'error': str(e)}
	counts = _index_counts(index)
	try:
		MainCache().set(cache_key, index, expiration=CACHE_DURATION)
		_log('Sync Cache Saved | Provider: %s | Key: %s | %s' % (provider_name, cache_key, _counts_log(index)))
	except Exception as e:
		_log('Sync Cache Save Error | Provider: %s | %s: %s' % (provider_name, type(e).__name__, e))
	_log('Sync Finished | Provider: %s | %s' % (provider_name, _counts_log(index)))
	return {'success': True, 'provider': provider_name, 'count': counts.get('total', 0), 'counts': counts, 'index': index}

def get_user_rating(index, media_type, tmdb_id=None, imdb_id=None, tvdb_id=None, season=None, episode=None):
	try:
		if media_type == 'show': media_type = 'tvshow'
		bucket = (index or {}).get(media_type) or {}
		for key in _id_keys(media_type, tmdb_id=tmdb_id, imdb_id=imdb_id, tvdb_id=tvdb_id, season=season, episode=episode):
			if key in bucket: return _normalize_rating(bucket[key])
	except:
		pass
	return None


def get_current_user_rating(params):
	media_type = params.get('media_type', 'movie')
	index = get_active_ratings_index()
	return get_user_rating(
		index,
		media_type,
		tmdb_id=params.get('tmdb_id'),
		imdb_id=params.get('imdb_id'),
		tvdb_id=params.get('tvdb_id'),
		season=params.get('season'),
		episode=params.get('episode')
	)


def clear_user_ratings_cache(provider=None):
	try:
		cache = MainCache()
		providers = range(3) if provider is None else (provider,)
		for item in providers:
			cache_key = '%s%s' % (CACHE_PREFIX, item)
			cache.delete(cache_key)
			_log('Cache Cleared | Provider: %s | Key: %s' % (_provider_label(item), cache_key))
	except Exception as e:
		_log('Cache Clear Error | %s: %s' % (type(e).__name__, e))




# Après une notation effectuée par l'addon officiel Trakt (script.trakt),
# alkoFlix ne reçoit pas d'événement direct. On invalide donc prudemment le
# cache Trakt après la lecture, avec deux délais, pour laisser le temps au
# dialogue de notation externe d'envoyer la note à Trakt.
def refresh_trakt_ratings_after_external_dialog(media_type=None, reason=''):
	try:
		if max(0, min(settings.ratings_provider(), 3)) != 0: return
		if media_type not in ('movie', 'movies', 'episode', None): return
		if not kodi_utils.addon_installed('script.trakt'): return
		if not _provider_authorized(0): return
		pending_key = 'alkoflix_trakt_external_ratings_refresh_pending'
		if kodi_utils.get_property(pending_key) == 'true':
			_log('External Refresh Skipped | Provider: Trakt | Reason: already pending | Source: %s' % (reason or 'unknown'))
			return
		kodi_utils.set_property(pending_key, 'true')

		def _worker():
			try:
				_log('External Refresh Scheduled | Provider: Trakt | Media: %s | Source: %s' % (media_type or 'unknown', reason or 'unknown'))
				for delay_ms in (8000, 22000):
					kodi_utils.sleep(delay_ms)
					clear_user_ratings_cache(0)
					_log('External Refresh Cache Cleared | Provider: Trakt | Delay: %sms | Media: %s | Source: %s' % (delay_ms, media_type or 'unknown', reason or 'unknown'))
			finally:
				try: kodi_utils.clear_property(pending_key)
				except: pass
		Thread(target=_worker).start()
	except Exception as e:
		_log('External Refresh Error | Provider: Trakt | %s: %s' % (type(e).__name__, e))

def apply_user_rating_to_listitem(listitem, user_rating, videoinfo=None):
	user_rating = _normalize_rating(user_rating)
	if user_rating is None: return
	try: listitem.setProperty('userrating', str(user_rating))
	except: pass
	try: listitem.setProperty('ListItem.UserRating', str(user_rating))
	except: pass
	try:
		if videoinfo is None:
			try: videoinfo = listitem.getVideoInfoTag(offscreen=True)
			except TypeError: videoinfo = listitem.getVideoInfoTag()
		videoinfo.setUserRating(user_rating)
	except:
		try: listitem.setInfo('video', {'userrating': user_rating})
		except: pass
