# -*- coding: utf-8 -*-
"""Media detail on Home: cached data, pinned hero and a reversible navigation stack.

Only an explicit open/season action fetches TMDb. The row endpoint reads the
canonical metadata cache and uses the library's cards, state and playback actions.
Window(Home) properties carry presentation/navigation state, never credentials.
"""
import json
import time
from datetime import date
from urllib.parse import urlencode

import xbmc
import xbmcaddon
import xbmcgui
import xbmcplugin

from wcs.library2 import clicks, meta, rows, tmdb
from wcs.skin import hero, performance
from wcs.skin.detail_contract import DETAIL_ROWS, PLAY, PREFIX

BASE = 'plugin://plugin.video.milionar/'
ROW_IDS = {kind: control for kind, control, _label, _style in DETAIL_ROWS}


def text(number):
    return xbmcaddon.Addon('plugin.video.milionar').getLocalizedString(number)


def builtin(action, **params):
    return 'RunPlugin({}?{})'.format(BASE.rstrip('/'), urlencode(dict(params, action=action)))


def _home():
    return xbmcgui.Window(10000)


def _get(name):
    return _home().getProperty(PREFIX + name)


def _set(name, value):
    _home().setProperty(PREFIX + name, str(value))


def _state():
    return json.loads(_get('state')) if _get('state') else None


def _focus():
    window = xbmcgui.getCurrentWindowId()
    control = xbmcgui.Window(window).getFocusId()
    position = xbmc.getInfoLabel('Container({}).CurrentItem'.format(control))
    return {'window': window, 'control': control, 'position': int(position or 1) - 1,
            'row': _home().getProperty('milionar.row.detail')}


def _restore(focus, menu=False):
    if menu:
        return
    if focus['window'] != xbmcgui.getCurrentWindowId():
        xbmc.executebuiltin('ActivateWindow({})'.format(focus['window']))
    # Native Home controls update visibility and directory content on the next GUI
    # frame. Restore the selection only once its target is ready, while the sidebar
    # exit guard remains set; otherwise Kodi falls back to the menu mid-transition.
    ready = 'Window.IsActive({}) + Control.IsVisible({}) + !Container({}).IsUpdating'.format(
        focus['window'], focus['control'], focus['control'])
    if focus['control'] in ROW_IDS.values():
        ready += ' + Integer.IsGreater(Container({}).NumItems,0)'.format(focus['control'])
    deadline = time.monotonic() + 2
    while not xbmc.getCondVisibility(ready) and time.monotonic() < deadline:
        xbmc.sleep(20)
    xbmc.executebuiltin('SetFocus({},{},absolute)'.format(focus['control'], focus['position']), True)
    xbmc.executebuiltin('ClearProperty(milionar.detail.entering,home)', True)


class DetailData(rows.Data):
    """TMDb list entries supply uncached card facts; library state still comes from Data."""
    def __init__(self, media, entries=()):
        super().__init__()
        self.media = media
        self.entries = {int(entry['id']): entry for entry in entries if entry.get('id')}

    def snapshot(self, media_type, tmdb_id):
        snapshot = dict(self.entries.get(tmdb_id) or {}) if media_type == self.media else {}
        snapshot.update(super().snapshot(media_type, tmdb_id))
        snapshot.setdefault('year', (snapshot.get('release_date') or snapshot.get('first_air_date') or '')[:4])
        return snapshot


def _card(media, tmdb_id, data=None):
    data = data or rows.Data()
    return rows.movie_card(data, tmdb_id) if media == 'movie' else rows.series_card(data, tmdb_id)


def _detail_data(state):
    key = meta.key_movie(state['id']) if state['media'] == 'movie' else meta.key_tv(state['id'])
    return meta.get(key)[0] or {}


def _entries(state, kind):
    data = _detail_data(state)
    if kind == 'seasons':
        return [s for s in data.get('seasons') or [] if s.get('episode_count')] if state['media'] == 'tv' else []
    if kind == 'episodes':
        season = meta.get(meta.key_season(state['id'], state['season']))[0] or {}
        return season.get('episodes') or []
    if kind == 'cast':
        return (data.get('credits') or {}).get('cast') or []
    if kind == 'collection':
        collection_id = (data.get('belongs_to_collection') or {}).get('id')
        collection = meta.get(meta.key_collection(collection_id))[0] if collection_id else None
        return sorted((collection or {}).get('parts') or [], key=lambda entry: entry.get('release_date') or '9999')
    entries = (data.get(kind) or {}).get('results') or []
    # Avoid the opened title and overlap between Recommendations and Similar.
    excluded = {state['id']}
    if kind == 'similar':
        excluded.update(entry.get('id') for entry in (data.get('recommendations') or {}).get('results') or [])
    seen, result = set(excluded), []
    for entry in entries:
        if entry.get('id') and entry['id'] not in seen:
            seen.add(entry['id'])
            result.append(entry)
    return result


def _trailer(data):
    videos = (data.get('videos') or {}).get('results') or []
    video = next((v for v in videos if v.get('site') == 'YouTube' and v.get('type') == 'Trailer' and v.get('key')), None)
    return 'plugin://plugin.video.youtube/?' + urlencode({'action': 'play_video', 'videoid': video['key']}) if video else ''


def _publish(state):
    """Commit one ready detail; row URLs change only after all properties are ready."""
    data = rows.Data()
    card = _card(state['media'], state['id'], data)
    fields = hero.movie(card) if state['media'] == 'movie' else hero.series(card)
    resumable, percent = bool(card.get('resume')), 0
    if state['media'] == 'tv':
        next_state = card['state']
        if next_state['kind'] == 'resume':
            ep = next_state['episode']
            saved = card['states'].get((ep['s'], ep['e'])) or {}
            fields = hero.episode(card, ep, saved)
            resumable = True
            percent = 100 * float(saved.get('resume_pos') or 0) / max(float(saved.get('resume_total') or 0), 1)
    elif card.get('resume_total'):
        percent = 100 * card['resume'] / card['resume_total']
    path, folder = clicks.title_path(state['media'], state['id'], 'play')
    fields.update(play=clicks.exec_for(path, folder), percent=percent,
                  resumable='true' if resumable else '',
                  plot_full=card['plot'], trailer=_trailer(_detail_data(state)))
    from wcs.library2 import menu
    fields['list_label'] = menu.Menu(data)._list_items(state['media'], state['id'])[0][0]
    fields['play_label'] = text(32683) if resumable else text(32402)
    fields.update(_recommendation(state['media'], state['id']))
    _set('entering', 'true')
    for name, value in fields.items():
        _set(name, value)
    _set('media', state['media'])
    _set('id', state['id'])
    for kind in ROW_IDS:
        visible = kind != 'episodes' or state['season'] is not None
        _set('has.' + kind, 'true' if visible and _entries(state, kind) else '')
    _set('episodes_label', '{} · {}'.format(text(32671), text(32674).format(state['season'])) if state['season'] is not None else '')
    _home().setProperty('milionar.row.detail', state.get('row', ''))
    _set('state', json.dumps(state))
    _set('title_key', '{}:{}:all'.format(state['media'], state['id']))
    _set('key', '{}:{}:{}'.format(state['media'], state['id'], state['season'] if state['season'] is not None else 'all'))
    _set('active', 'true')


def _recommendation(media, tmdb_id):
    """A title of the Pro tebe page shows its match and reason in the detail too ({} when it is not there)."""
    try:
        from wcs.foryou import engine, widgets
        snapshot = engine.load()
        entry = (snapshot or {}).get('items', {}).get('{}:{}'.format(media, int(tmdb_id)))
        if not entry or not entry.get('match'):
            return {'badge': '', 'now': ''}
        return {'badge': widgets._text(widgets.S_MATCH).format(entry['match']).upper(),
                'now': widgets.reason_text(snapshot, entry.get('reason'))}
    except Exception as exc:                       # the detail never fails because of a recommendation
        xbmc.log('[Milionář Detail] recommendation: {}: {}'.format(type(exc).__name__, exc), xbmc.LOGWARNING)
        return {'badge': '', 'now': ''}


def open_detail(params):
    media = params.get('media_type')
    if media not in ('movie', 'tv') or not str(params.get('tmdb_id', '')).isdigit():
        return
    tmdb_id = int(params['tmdb_id'])
    previous = _state() if _get('active') else None
    focus = _focus()
    xbmc.executebuiltin('ActivateWindow(busydialognocancel)')
    try:
        data = tmdb.detail(media, tmdb_id)
        collection_id = (data.get('belongs_to_collection') or {}).get('id')
        if collection_id:
            tmdb.collection(collection_id)
        stack = json.loads(_get('stack') or '[]') if previous else []
        if previous:
            if (previous['media'], previous['id'], previous['season']) != (media, tmdb_id, None):
                previous['row'] = focus['row']
                stack.append({'state': previous, 'focus': focus})
            origin = json.loads(_get('origin'))
        else:
            origin = focus
        state = {'media': media, 'id': tmdb_id, 'season': None, 'row': '',
                 'ctx': params.get('ctx', ''), 'ctxrow': params.get('ctxrow', '')}
        _publish(state)
        _set('origin', json.dumps(origin))
        _set('stack', json.dumps(stack))
    except Exception as exc:
        xbmc.log('[Milionář Detail] open failed: {}: {}'.format(type(exc).__name__, exc), xbmc.LOGERROR)
        xbmcgui.Dialog().notification('Milionář', text(32675), xbmcgui.NOTIFICATION_ERROR)
        return
    finally:
        xbmc.executebuiltin('Dialog.Close(busydialognocancel,true)')
    # Search is a native skin window; returning to it retains its cached results.
    _restore({'window': 10000, 'control': PLAY, 'position': 0})


def back(root=False, menu=False):
    if not _get('active'):
        return
    stack = json.loads(_get('stack') or '[]')
    if stack and not root:
        frame = stack.pop()
        _publish(frame['state'])
        _set('stack', json.dumps(stack))
        _restore(frame['focus'])
        return
    origin = json.loads(_get('origin'))
    _home().clearProperty(PREFIX + 'active')
    _home().clearProperty(PREFIX + 'state')
    _home().clearProperty(PREFIX + 'stack')
    _home().clearProperty(PREFIX + 'key')
    _restore(origin, menu)


def season(number):
    state = _state()
    if not state or state['media'] != 'tv':
        return
    number = int(number)
    if number not in [int(s['season_number']) for s in _detail_data(state).get('seasons') or []]:
        return
    focus = _focus()
    xbmc.executebuiltin('ActivateWindow(busydialognocancel)')
    try:
        tmdb.season(state['id'], number, strict=True)
        stack = json.loads(_get('stack') or '[]')
        previous = dict(state, row=focus['row'])
        stack.append({'state': previous, 'focus': focus})
        _publish(dict(state, season=number, row=str(ROW_IDS['episodes'])))
        _set('stack', json.dumps(stack))
    except Exception as exc:
        xbmc.log('[Milionář Detail] season failed: {}'.format(exc), xbmc.LOGERROR)
        xbmcgui.Dialog().notification('Milionář', text(32675), xbmcgui.NOTIFICATION_ERROR)
        return
    finally:
        xbmc.executebuiltin('Dialog.Close(busydialognocancel,true)')
    _restore({'window': 10000, 'control': ROW_IDS['episodes'], 'position': 0})


def _action_item(label, action, art='', label2='', **params):
    path = BASE + '?' + urlencode(dict(params, action=action))
    item = xbmcgui.ListItem(label=label, label2=label2, path=path, offscreen=True)
    rows._set_art(item, {'thumb': art, 'poster': art, 'icon': art})
    item.setProperty('milionar.exec', builtin(action, **params))
    item.setProperty('IsPlayable', 'true')
    return path, item, False


def items(state, kind):
    entries = _entries(state, kind)
    if kind == 'cast':
        return [_action_item(entry['name'], 'skin_detail_actor',
                             rows._image(rows.POSTER, entry.get('profile_path')), entry.get('character', ''),
                             actor_id=entry['id'], actor_name=entry['name']) for entry in entries[:performance.limit(30)] if entry.get('id')]
    if kind == 'seasons':
        return [_action_item(entry.get('name') or text(32674).format(entry['season_number']), 'skin_detail_season',
                             rows._image(rows.POSTER, entry.get('poster_path')), '{}: {}'.format(text(32671), entry['episode_count']),
                             s=entry['season_number'])
                for entry in entries if entry.get('episode_count')]
    if kind == 'episodes':
        from wcs.library2 import menu
        data = rows.Data()
        card = _card('tv', state['id'], data)
        context = menu.Menu(data)
        result = []
        for entry in entries:
            when = entry.get('air_date') or ''
            ep = {'s': state['season'], 'e': entry['episode_number'], 'name': entry.get('name'),
                  'air_date': when, 'still': entry.get('still_path')}
            path, item, folder = rows.episode_item(card, ep, 'next' if when and when <= date.today().isoformat() else 'upcoming')
            item.setProperty('milionar.hero.title', entry.get('name') or item.getLabel())
            item.setProperty('milionar.exec', clicks.exec_for(path, folder))
            if when and when <= date.today().isoformat():
                menu.attach(item, context, 'episode', state['id'], state['season'], ep['e'])
            result.append((path, item, folder))
        return result
    from wcs.library2 import menu
    data = DetailData(state['media'], entries)
    context = menu.Menu(data)
    result = []
    for entry in entries[:performance.limit(30)]:
        card = _card(state['media'], int(entry['id']), data)
        path, item, folder = (rows.movie_item(card) if state['media'] == 'movie' else rows.series_item(card, click='detail'))
        item.setProperty('milionar.exec', clicks.exec_for(path, folder))
        menu.attach(item, context, state['media'], card['id'])
        result.append((path, item, folder))
    return result


def row(handle, params):
    # Read the URL's immutable identity, not the currently opened detail: an old request
    # may finish while the user is already on another title.
    media, tmdb_id, season_no = params['key'].split(':')
    state = {'media': media, 'id': int(tmdb_id), 'season': None if season_no == 'all' else int(season_no)}
    result = items(state, params['kind'])
    xbmcplugin.addDirectoryItems(handle, result, len(result))
    xbmcplugin.endOfDirectory(handle, cacheToDisc=False)


def actor(params):
    # Same choices as the original details dialog; filmography still uses its proven dialog.
    labels = [text(32676), text(32677), text(32417), text(32678)]
    picked = xbmcgui.Dialog().select(params['actor_name'], labels)
    if picked in (0, 1):
        command = builtin('display_actor_dialog', actor_id=params['actor_id'], actor_name=params['actor_name'],
                          media_type='movie' if picked == 0 else 'tv')
    elif picked == 2:
        command = builtin('milionar_game', tmdb_id=params['actor_id'], title=params['actor_name'], media_type='actor')
    elif picked == 3:
        command = builtin('tmdb_add_actor_to_favorites', actor_id=params['actor_id'], actor_name=params['actor_name'])
    else:
        return
    from wcs.skin import context
    state = _state() or {}
    command = 'RunPlugin(' + context.with_origin(command[10:-1], params.get('ctx') or state.get('ctx'),
                                               params.get('ctxrow') or state.get('ctxrow', '')) + ')'
    xbmc.executebuiltin(command)


def actions():
    state = _state()
    if not state:
        return
    from wcs.skin import context
    available = {'plot': (text(32679), builtin('skin_detail_plot'))}
    if _get('trailer') and xbmc.getCondVisibility('System.AddonIsEnabled(plugin.video.youtube)'):
        available['trailer'] = (context.text(32739), builtin('skin_detail_trailer'))
    detail_data = _detail_data(state)
    if state['media'] == 'movie':
        director = next((p for p in (detail_data.get('credits') or {}).get('crew') or [] if p.get('job') == 'Director'), None)
        if director:
            available['director'] = (context.text(32738) + ': ' + director['name'],
                                     builtin('display_director_dialog', director_id=director['id'], director_name=director['name']))
    elif detail_data.get('created_by'):
        available['creators'] = (text(32680), builtin('display_series_creators_dialog', tmdb_id=state['id'], title=_get('title')))
    context.open_menu({'type': state['media'], 'tmdb': str(state['id']), 'title': _get('title'),
                       'ctx': state.get('ctx', ''), 'row': state.get('ctxrow', '')}, actions=available)


def run(handle, params):
    action = params['action']
    if action == 'skin_detail_row':
        row(handle, params)
    elif action == 'skin_detail_info':
        xbmc.executebuiltin('Dialog.Close(12003,true)', True)
        open_detail(dict(params, media_type='movie' if params.get('media_type') == 'movie' else 'tv'))
    elif action == 'skin_detail_back':
        back(params.get('root') == '1', params.get('menu') == '1')
    elif action == 'skin_detail_season':
        season(params['s'])
    elif action == 'skin_detail_actor':
        actor(params)
    elif action == 'skin_detail_actions':
        actions()
    elif action == 'skin_detail_plot':
        xbmcgui.Dialog().textviewer(_get('title'), _get('plot_full'))
    elif action == 'skin_detail_list':
        from wcs.library2 import ctx
        state = _state()
        if state:
            focus = _focus()
            ctx.toggle_list(state['media'], state['id'])
            _publish(dict(state, row=focus['row']))
            _restore(focus)
    elif action == 'skin_detail_trailer':
        if _get('trailer') and xbmc.getCondVisibility('System.AddonIsEnabled(plugin.video.youtube)'):
            xbmc.executebuiltin('PlayMedia({})'.format(_get('trailer')))
