# -*- coding: utf-8 -*-
"""Can a recommendation be played? The Webshare check of Pro tebe (docs/plan/pro-tebe/020).

The check runs the same search as playback (wcs/ui/dialogs/DialogWcsSearch.py): the same queries
(ranking.movie_queries / episode_queries + the dialog's own query and fallbacks), the same strict ranking
(ranking.rank: only files that are the requested film / episode), so "available" means the play dialog will have
that file on top. A series is checked on S01E01, what a household starts with. The language of the best file decides:
CZ dub (or bare CZ), CZ subtitles and SK dub count (decision 10 and 2026-10-09: "CZ + SK dabing"); English or
unlabelled files do not. Results are cached in `meta` (key `avail:<media>:<id>`); errors are never cached.
"""
import time
import xml.etree.ElementTree as ET

import xbmc

from wcs.search import ranking

KEY = 'avail:{}:{}'
DAY = 86400
TTL_FOUND = 7 * DAY
TTL_MISSING = 3 * DAY
TTL_MISSING_NEW = DAY // 2                 # a new film appears on Webshare any day
NEW_DAYS = 120
LANGS = {'CZ_DUB': 'cz_dub', 'CZ': 'cz_dub', 'CZ_SUB': 'cz_sub', 'SK_DUB': 'sk_dub'}
SEARCH = None                              # tests: function(query) -> [file dicts]; None = Webshare


class Unavailable(Exception):
    """Webshare cannot be asked (no account, login failed, network): nothing is cached."""


def _query_webshare(query, token):
    from wcs.webshare import WebshareClient
    response = WebshareClient.api_call('search', {'what': query, 'category': 'video', 'sort': '', 'limit': 50,
                                                  'offset': 0, 'wst': token})
    root = ET.fromstring(response.content)
    if not WebshareClient.is_ok(root):
        raise Unavailable('search {!r}: {}'.format(query, root.findtext('message') or root.findtext('status')))
    files = []
    for node in root.iter('file'):
        item = {e.tag: e.text for e in node}
        if item.get('ident'):
            item['name'] = item.get('name') or ''
            files.append(item)
    return files


def token():
    """A valid Webshare token or Unavailable (no account / login failed); never shows a dialog."""
    import xbmcaddon
    addon = xbmcaddon.Addon('plugin.video.milionar')
    if not (addon.getSetting('token') or (addon.getSetting('wsuser') and addon.getSetting('wspass'))):
        raise Unavailable('no Webshare account')
    from wcs.webshare import WebshareClient
    value = WebshareClient.revalidate(addon, lambda *a, **k: None)
    if not value:
        raise Unavailable('Webshare login failed')
    return value


def _search(query, wst):
    if SEARCH is not None:
        return SEARCH(query)
    try:
        return _query_webshare(query, wst)
    except (ET.ParseError, OSError, Unavailable):          # Webshare sometimes answers garbage under load
        time.sleep(1.0)
        return _query_webshare(query, wst)


def ambiguous(card):
    """A one-word Czech title that is not the original ('Rodiče' = Breeders) can be another production."""
    title = ranking.norm(card['title'])
    original = ranking.norm(card.get('original_title') or '')
    return len(title.split()) == 1 and original and original != title


def confident(file_info, card):
    """For an ambiguous title the file must also name the original title or the year."""
    if not ambiguous(card):
        return True
    original = ranking.norm(card.get('original_title') or '')
    year = int(card.get('year') or 0)
    named = (' ' + original + ' ') in (' ' + file_info['norm'] + ' ')
    dated = bool(year) and any(abs(y - year) <= 1 for y in file_info['years'])
    return named or dated


def plan(card):
    """(target, queries, fallback) exactly as the play dialog builds them for this title."""
    title = card['title']
    original = card.get('original_title') or ''
    if original.strip().lower() == title.strip().lower():
        original = ''
    if card['media'] == 'movie':
        year = str(card.get('year') or '')
        target = ranking.make_target('movie', [title, original], year=year)
        queries = ['{} {}'.format(title, year).strip()] + ranking.movie_queries([title, original], year)
        fallback = [title] if year else []
    else:
        target = ranking.make_target('episode', [title, original], season=1, episode=1)
        first = '{} S01E01'.format(title)
        queries = [first] + ranking.episode_queries([title, original], 1, 1)
        fallback = ['retry:' + first]                 # SeriesSearch.do_webshare_search_with_retry
    return target, list(dict.fromkeys(q for q in queries if q)), fallback


def _retry(query, wst):
    """do_webshare_search_with_retry without the addon object: shorten the title before SxxEyy until files come."""
    import re
    files = _search(query, wst)
    if files:
        return files
    match = re.search(r'\s+(S\d+E\d+.*)$', query, re.IGNORECASE)
    if not match:
        return files
    name = query[:match.start()]
    while len(name) > 5:
        name = name[:-1].rstrip()
        if len(name) < 5:
            break
        files = _search('{}{}'.format(name, match.group(0)), wst)
        if files:
            return files
    return files


def check(card, wst, preferred_quality=None):
    """{'status': 'ok'|'missing', 'lang', 'quality', 'files', 'at'} of one title (raises Unavailable)."""
    target, queries, fallback = plan(card)
    found = {}
    for query in queries:
        for item in _search(query, wst):
            found.setdefault(item['ident'], item)
    ranked = ranking.rank(list(found.values()), target, preferred_quality)
    if not ranked:
        for query in fallback:
            items = _retry(query[6:], wst) if query.startswith('retry:') else _search(query, wst)
            for item in items:
                found.setdefault(item['ident'], item)
        ranked = ranking.rank(list(found.values()), target, preferred_quality)
    # a password-protected file cannot be played; the best playable one decides
    playable = [f for f in ranked if str(f.get('password') or '0') != '1' and confident(f['info'], card)]
    best = playable[0] if playable else None
    lang = LANGS.get(best['info']['lang']) if best else None
    return {'status': 'ok' if lang else 'missing', 'lang': lang or '',
            'quality': best['info']['quality'] if best else '', 'files': len(playable), 'at': int(time.time()),
            'top': (best or {}).get('name', '')[:120]}


def cached(media, tmdb_id):
    from wcs.library2 import meta
    data, stale = meta.get(KEY.format(media, int(tmdb_id)))
    return None if data is None or stale else data


def _ttl(result, card, today):
    if result['status'] == 'ok':
        return TTL_FOUND
    date = card.get('date') or ''
    try:
        from datetime import date as _date
        if date and (today - _date.fromisoformat(date)).days <= NEW_DAYS:
            return TTL_MISSING_NEW
    except ValueError:
        pass
    return TTL_MISSING


def check_many(cards, budget=None, workers=4, today=None, log=None):
    """{key: result} for every card: cached ones as they are, the rest checked on Webshare (at most `budget`
    titles, in the given order). Raises Unavailable when Webshare cannot be asked at all."""
    from datetime import date
    from wcs.library2 import meta
    from wcs.foryou import api
    today = today or date.today()
    results, todo = {}, []
    for card in cards:
        key = '{}:{}'.format(card['media'], card['id'])
        hit = cached(card['media'], card['id'])
        if hit is not None:
            results[key] = hit
        elif budget is None or len(todo) < budget:
            todo.append((key, card))
    if not todo:
        return results
    wst = token() if SEARCH is None else 'test'
    preferred = _preferred_quality()
    errors = []

    def one(pair):
        key, card = pair
        try:
            return key, card, check(card, wst, preferred)
        except Exception as exc:                  # one title failing never stops the others
            errors.append('{}: {}: {}'.format(key, type(exc).__name__, exc))
            return key, card, None
    entries = []
    for answer in api.parallel([lambda p=p: one(p) for p in todo], workers):
        if answer and answer[2]:
            key, card, result = answer
            results[key] = result
            entries.append((KEY.format(card['media'], card['id']), result, _ttl(result, card, today)))
    meta.put_many(entries)
    if errors:
        xbmc.log('[Milionář ProTebe] {} Webshare checks failed, e.g. {}'.format(len(errors), errors[0]), xbmc.LOGWARNING)
    if log:
        log('webshare: {} checked ({} ok), {} cached, {} failed'.format(
            len(entries), sum(1 for _k, r, _t in entries if r['status'] == 'ok'), len(results) - len(entries), len(errors)))
    return results


def _preferred_quality():
    """The play dialog's preferred quality (setting autoplay_preferred_quality), so the reported quality matches."""
    try:
        import xbmcaddon
        value = xbmcaddon.Addon('plugin.video.milionar').getSettingInt('autoplay_preferred_quality')
    except Exception:
        return None
    return {1: '4K', 2: '1080p', 3: '720p', 4: '480p', 5: '360p', 6: 'SD'}.get(value)
