import json
import sys
import unicodedata
from datetime import timedelta
from urllib.parse import unquote
from windows import open_window
from indexers.tmdb_api import tmdb_people_info, tmdb_people_popular_media, tmdb_people_full_info, tmdb_image_base
from menus.images import Images
from modules.kodi_utils import media_path, select_dialog, dialog, notification, build_url, make_listitem, add_item, add_items, add_dir, end_directory, set_category, set_content, set_view_mode, get_addoninfo, local_string, external_browse, set_listitem_video_info, logger, get_setting, clean_art
from modules.info_windows import person_window_xml
from caches.favourites_cache import favourites_context_label
from caches.main_cache import MainCache
# from modules.kodi_utils import logger



NON_LATIN_PERSON_SCRIPTS = (
	'CJK', 'HIRAGANA', 'KATAKANA', 'HANGUL', 'BOPOMOFO',
	'THAI', 'LAO', 'KHMER', 'MYANMAR', 'DEVANAGARI', 'BENGALI',
	'GURMUKHI', 'GUJARATI', 'ORIYA', 'TAMIL', 'TELUGU', 'KANNADA',
	'MALAYALAM', 'SINHALA', 'TIBETAN', 'MONGOLIAN'
)

# Évite les noms en écritures non latines qui peuvent être illisibles selon la police du skin.
def _valid_person_name(name):
	try:
		name = str(name or '').strip()
		if not name: return False
		for char in name:
			if char.isspace() or char in "-.'’`´": continue
			unicode_name = unicodedata.name(char, '')
			if any(script in unicode_name for script in NON_LATIN_PERSON_SCRIPTS): return False
		return True
	except: return False


def _known_for_text(item, media_type=None):
	try:
		known_for = item.get('known_for', []) or []
		if media_type: known_for = [i for i in known_for if i.get('media_type') == media_type]
		titles = [i.get('title') or i.get('name') for i in known_for if (i.get('title') or i.get('name'))]
		return ', '.join(titles[:3])
	except: return ''

poster_empty = media_path('people.png')

PERSON_ROLES_EXCLUDE = ('himself', 'herself', 'self', 'narrator', 'voice (voice)')
PERSON_TV_GENRES_EXCLUDE = (10763, 10764, 10767)
_PERSON_CREDITS_MEMORY = {}

def _person_credits_debug(action, actor_id=None, credit_type=None, count=None, extra=''):
	# Diagnostic strictement limité au mode debug alkoFlix. En mode normal,
	# les crédits Personnes restent silencieux pour ne pas polluer le log.
	try:
		if get_setting('debug.enabled', 'false') != 'true': return
		parts = [action]
		if actor_id not in ('', None): parts.append('actor_id=%s' % actor_id)
		if credit_type: parts.append('type=%s' % credit_type)
		if count is not None: parts.append('ids=%s' % count)
		if extra: parts.append(str(extra))
		logger('Person credits', ' | '.join(parts))
	except: pass

def _person_credit_cache_key(actor_id, credit_type, exclude_non_acting=None):
	# Le cache inclut le réglage d'exclusion pour éviter de mélanger deux listes.
	return 'alkoflix_person_credit_ids_%s_%s_%s' % (actor_id, credit_type, 'filtered' if exclude_non_acting else 'all')


def _person_credit_ids_from_data(data, credit_type, exclude_non_acting=None):
	try:
		if exclude_non_acting is None:
			from modules.settings import extras_exclude_non_acting
			exclude_non_acting = extras_exclude_non_acting()
		if credit_type == 'movie':
			release_key = 'release_date'
			data = [i for i in data if i.get('media_type', 'movie') == 'movie']
			if exclude_non_acting:
				data = [i for i in data if not 99 in (i.get('genre_ids') or []) and not str(i.get('character') or '').lower() in PERSON_ROLES_EXCLUDE]
		elif credit_type == 'tvshow':
			release_key = 'first_air_date'
			data = [i for i in data if i.get('media_type', 'tv') == 'tv']
			if exclude_non_acting:
				data = [i for i in data if not any(x in (i.get('genre_ids') or []) for x in PERSON_TV_GENRES_EXCLUDE) and not str(i.get('character') or '').lower() in PERSON_ROLES_EXCLUDE]
		else:
			release_key = 'release_date'
			data = [i for i in data if i.get('media_type', 'movie') == 'movie' and str(i.get('job') or 'director').lower() == 'director']
		ids, used = [], set()
		for item in sorted(data, key=lambda x: x.get(release_key) or '', reverse=True):
			try:
				media_id = item.get('id')
				if not media_id or media_id in used: continue
				used.add(media_id)
				ids.append(media_id)
			except: pass
		return ids
	except:
		return []


def set_person_credit_ids_cache(actor_id, credit_type, data, exclude_non_acting=None):
	# La fiche Personne a déjà les crédits TMDb en mémoire. On sauvegarde donc
	# seulement la liste d'identifiants, pour que le bouton Films/Séries/Réalisations
	# ouvre exactement la même filmographie sans refaire une lecture complète.
	try:
		ids = _person_credit_ids_from_data(data or [], credit_type, exclude_non_acting)
		key = _person_credit_cache_key(actor_id, credit_type, exclude_non_acting)
		_PERSON_CREDITS_MEMORY[key] = ids
		MainCache().set(key, ids, expiration=timedelta(hours=4))
		_person_credits_debug('prepared cache set', actor_id, credit_type, len(ids), 'source=person_window')
		return ids
	except Exception as exc:
		_person_credits_debug('prepared cache set failed', actor_id, credit_type, None, '%s: %s' % (type(exc).__name__, exc))
		return []


# Retourne les identifiants complets des crédits TMDb d'une personne.
# Cette liste sert aux boutons de la fenêtre d'information personne et doit
# rester alignée avec les sections affichées au centre de cette fenêtre.
def person_credit_ids(actor_id, credit_type, exclude_non_acting=None):
	try:
		if exclude_non_acting is None:
			from modules.settings import extras_exclude_non_acting
			exclude_non_acting = extras_exclude_non_acting()
		key = _person_credit_cache_key(actor_id, credit_type, exclude_non_acting)
		if key in _PERSON_CREDITS_MEMORY:
			ids = _PERSON_CREDITS_MEMORY.get(key) or []
			_person_credits_debug('memory hit', actor_id, credit_type, len(ids))
			return ids
		try:
			cached_ids = MainCache().get(key)
			if cached_ids is not None:
				ids = cached_ids or []
				_PERSON_CREDITS_MEMORY[key] = ids
				_person_credits_debug('prepared cache hit', actor_id, credit_type, len(ids))
				return ids
		except Exception as exc:
			_person_credits_debug('prepared cache read failed', actor_id, credit_type, None, '%s: %s' % (type(exc).__name__, exc))
		_person_credits_debug('prepared cache miss', actor_id, credit_type)
		person_info = tmdb_people_full_info(actor_id) or {}
		credits = person_info.get('combined_credits') or {}
		if credit_type == 'movie': data = credits.get('cast', [])
		elif credit_type == 'tvshow': data = credits.get('cast', [])
		else: data = credits.get('crew', [])
		ids = _person_credit_ids_from_data(data, credit_type, exclude_non_acting)
		_PERSON_CREDITS_MEMORY[key] = ids
		try: MainCache().set(key, ids, expiration=timedelta(hours=4))
		except Exception as exc: _person_credits_debug('fallback cache set failed', actor_id, credit_type, None, '%s: %s' % (type(exc).__name__, exc))
		_person_credits_debug('fallback tmdb credits loaded', actor_id, credit_type, len(ids))
		return ids
	except Exception as exc:
		_person_credits_debug('credits load failed', actor_id, credit_type, None, '%s: %s' % (type(exc).__name__, exc))
		return []


def popular_people():
	Images().run({'mode': 'popular_people_image_results', 'page_no': 1})

def person_data_dialog(params):
	if 'query' in params: query = unquote(params['query'])
	else: query = None
	open_window(
		('windows.people', 'People'),
		person_window_xml(),
		query=query,
		actor_id=params.get('actor_id'),
		actor_name=params.get('actor_name'),
		actor_image=params.get('actor_image')
	)

def _person_search_dialog(query=None):
	try: actors = tmdb_people_info(query)
	except: actors = None
	if not actors: return notification(32760)
	for item in actors:
		known_for_list = [i.get('title') or i.get('name') for i in item.get('known_for', []) if i.get('title') or i.get('name')]
		item['icon'] = tmdb_image_base % ('h632', item['profile_path']) if item.get('profile_path') else poster_empty
		item['line1'] = item.get('name', '')
		item['line2'] = ', '.join(known_for_list) if known_for_list else ''
	if len(actors) > 1:
		kwargs = {'items': json.dumps(actors), 'heading': 'alkoFlix', 'multi_line': 'true'}
		selection = select_dialog(actors, **kwargs)
		if selection is None: return
	else: selection = actors[0]
	actor = int(selection['id']), selection['name'], selection['icon']
	if not actor: return
	return person_data_dialog({'actor_id': actor[0], 'actor_name': actor[1], 'actor_image': actor[2]})


def person_search(query=None):
	try:
		handle = int(sys.argv[1])
	except:
		handle = -1
	# Depuis un skin ou un dossier de recherche, on doit retourner une vraie liste
	# de personnes, sans popup. En mode RunPlugin/dialogue, on conserve le
	# comportement historique pour compatibilité.
	if handle >= 0:
		return PeopleMenu({'handle': handle, 'query': query, 'name': 'Recherche personnes'}).search()
	return _person_search_dialog(query)



class PeopleMenu:
	def __init__(self, params):
		params['handle'] = int(sys.argv[1])
		params['fanart'] = ''
		self.params = params
		self.params_get = params.get
		self.handle = params['handle']
		self.fanart = params['fanart']
		self.next_icon = media_path('item_next.png')

	def root(self):
		n_ins = 'Personnes'
		add_dir(self.handle, {'mode': 'search_history', 'action': 'people', 'name': 'Recherche'}, 'Recherche', media_path('search.png'))
		add_dir(self.handle, {'mode': 'people.results', 'action': 'popular_movies', 'name': 'Personnes populaires dans les films'}, 'Personnes populaires dans les films', media_path('movies.png'))
		add_dir(self.handle, {'mode': 'people.results', 'action': 'popular_tvshows', 'name': 'Personnes populaires dans les séries'}, 'Personnes populaires dans les séries', media_path('tv.png'))
		add_dir(self.handle, {'mode': 'people.favourites', 'name': 'Favoris (alkoFlix)'}, 'Favoris (alkoFlix)', media_path('favourites.png'))
		set_category(self.handle, n_ins)
		set_content(self.handle, '')
		end_directory(self.handle)
		set_view_mode('view.main', '')

	def search(self):
		query = unquote(self.params_get('query', '') or '').strip()
		try: actors = tmdb_people_info(query) if query else []
		except: actors = []
		items = self._build_people_items(actors or [], None)
		if items: add_items(self.handle, items)
		elif not external_browse(): notification(32760)
		set_category(self.handle, query or self.params_get('name', 'Recherche personnes'))
		set_content(self.handle, 'actors')
		end_directory(self.handle)
		set_view_mode('view.main', '')


	def favourites(self):
		from caches.favourites_cache import favourites_cache, has_favourites_types
		people = []
		# Les images ne sont pas stockées dans favourites.db : elles sont donc
		# récupérées depuis le cache TMDb au moment d'afficher le dossier.
		for item in favourites_cache.get_favourites(self.params_get('fav_type', 'person')):
			person_id = item.get('tmdb_id')
			name = item.get('title')
			try: person = tmdb_people_full_info(person_id) or {}
			except: person = {}
			if person.get('name'): name = person.get('name')
			people.append({
				'id': person_id,
				'name': name,
				'profile_path': person.get('profile_path') or '',
				'known_for': person.get('known_for') or []
			})
		items = self._build_people_items(people, None)
		if items: add_items(self.handle, items)
		set_category(self.handle, self.params_get('name', 'Favoris (alkoFlix)'))
		set_content(self.handle, 'actors')
		end_directory(self.handle)
		set_view_mode('view.main', '')

	def results(self):
		action = self.params_get('action', 'popular_movies')
		try: page_no = int(self.params_get('new_page', '1'))
		except: page_no = 1
		media_type = 'tv' if 'tvshow' in action else 'movie'
		data = tmdb_people_popular_media(media_type, page_no) or {}
		items = self._build_people_items(data.get('results', []), media_type)
		if items: add_items(self.handle, items)
		try:
			if int(data.get('total_pages', 1)) > page_no:
				next_params = {
					'mode': 'people.results', 'action': action, 'new_page': str(page_no + 1),
					'name': self.params_get('name', 'Personnes')
				}
				add_dir(self.handle, next_params, local_string(32799), self.next_icon)
		except: pass
		set_category(self.handle, self.params_get('name', 'Personnes'))
		set_content(self.handle, 'actors')
		end_directory(self.handle)
		set_view_mode('view.main', '')

	def _build_people_items(self, people, media_type):
		built = []
		for item in people:
			try:
				name = item.get('name') or ''
				if not _valid_person_name(name): continue
				profile = item.get('profile_path')
				poster = tmdb_image_base % ('w185', profile) if profile else poster_empty
				image = tmdb_image_base % ('h632', profile) if profile else poster_empty
				url = build_url({'mode': 'person_data_dialog', 'actor_name': name, 'actor_id': item.get('id'), 'actor_image': image})
				known_for = _known_for_text(item, media_type)
				listitem = make_listitem()
				listitem.setLabel(name)
				listitem.setArt(clean_art({'icon': poster, 'poster': poster, 'thumb': poster, 'fanart': self.fanart, 'banner': poster}))
				try:
					set_listitem_video_info(listitem, {'title': name, 'plot': known_for, 'mediatype': 'actor'})
				except: pass
				try:
					listitem.addContextMenuItems([(favourites_context_label('person', item.get('id')), 'RunPlugin(%s)' % build_url({'mode': 'favourites_choice', 'media_type': 'person', 'fav_type': 'person', 'tmdb_id': item.get('id'), 'title': name}))])
				except: pass
				built.append((url, listitem, False))
			except: pass
		return built
