# -*- coding: utf-8 -*-
"""One ranking for every place that shows or plays search results.

Pure Python (no Kodi imports) so scripts/search_harness can run it offline.

    target = make_target('episode', ['Přátelé', 'Friends'], season=1, episode=2)
    ranked = rank(files, target, preferred_quality='1080p')
    first = pick(ranked)          # None when nothing is the requested title

Only files that are the requested movie/episode are returned (never another
title); manual search ('any') keeps everything. Order: language (CZ dub > CZ subs
> SK dub > SK subs > other) > quality (the preferred one, else the best) >
votes > size. The dialog rows, the chips and autoplay all read the same
`info` dict that parse() attaches, so what is shown is what is ranked.
"""

import re
import unicodedata

QUALITIES = ['SD', '360p', '480p', '720p', '1080p', '4K']
LANG_RANK = {'CZ_DUB': 5, 'CZ': 5, 'CZ_SUB': 4, 'SK_DUB': 3, 'SK_SUB': 2, 'EN': 0, 'OTHER': 0}
LANG_CHIP = {'CZ_DUB': 'CZ dabing', 'CZ': 'CZ', 'CZ_SUB': 'CZ titulky', 'SK_DUB': 'SK dabing',
             'SK_SUB': 'SK titulky', 'EN': 'EN', 'OTHER': ''}

# Files below this size are samples/trailers and are dropped (MB)
MIN_SIZE_MB = {'movie': 100, 'episode': 60}
# Smaller than this for the claimed quality = mislabelled or broken; ranked as SD (MB)
BELIEVABLE_MB = {'movie': {'4K': 1500, '1080p': 500, '720p': 300},
                 'episode': {'4K': 300, '1080p': 100, '720p': 60}}

_SE_PATTERNS = [re.compile(r'\bs(\d{1,2}) ?e(\d{1,3})\b'),       # s01e02, s1 e2
                re.compile(r'\b(\d{1,2})x(\d{2,3})\b'),           # 1x02, 01x02
                re.compile(r'\bserie (\d{1,2}) dil (\d{1,3})\b')]
_CZ = {'cz', 'czech', 'cesky', 'cestina', 'czaudio', 'czdab', 'czdabing', 'czdub', 'cztit', 'czsub',
       'czen', 'czsk', 'czeng', 'cze'}
_SK = {'sk', 'slovak', 'slovensky', 'skdab', 'skdabing', 'skdub', 'sktit', 'sksub'}
_DUB = re.compile(r'\b(dab|dabing|dabingem|dabovane|dub|dubbing|audio|czaudio|czdab|czdabing|czdub|'
                  r'skdab|skdabing|skdub)\b')
_SUB = re.compile(r'\b(tit|titulky|titulkama|titulek|sub|subs|subti|subtitle|subtitles|cztit|czsub|sktit|sksub)\b')
_EN = {'en', 'eng', 'english'}
_YEAR = re.compile(r'^(19[2-9]\d|20[0-4]\d)$')
_STOP = _CZ | _SK | _EN | {
    '2160p', '1080p', '1080i', '720p', '576p', '480p', '360p', '4k', 'uhd', 'uhdr', 'fhd', 'fullhd', 'hd', 'sd',
    'bluray', 'bdrip', 'brrip', 'bd', 'web', 'webrip', 'webdl', 'dl', 'hdtv', 'tvrip', 'dvdrip', 'dvd', 'dvb',
    'x264', 'x265', 'h264', 'h265', 'hevc', 'avc', 'xvid', 'divx', 'remux', 'hdr', 'hdr10', 'dv', 'hlg',
    'ac3', 'dts', 'aac', 'mkv', 'avi', 'mp4', 'dab', 'dabing', 'tit', 'titulky', 'sub', 'dub', 'multi',
    'multidub', 'extended', 'remastered', 'unrated', 'directors', 'proper', 'complete'}
_FILLER = {'the', 'a', 'aka', 'serial', 'film', 'movie', 'tv', 'open', 'matte', 'edition', 'cut', 'uncut',
           'verze', 'verzia', 'rozsirena', 'prodlouzena', 'predlzena', 'special', 'final', 'theatrical', 'imax'}


def norm(text):
    """Lowercase ASCII words: 'Přátelé.S01E02' -> 'pratele s01e02'."""
    text = unicodedata.normalize('NFKD', text or '')
    text = ''.join(ch for ch in text if not unicodedata.combining(ch)).lower()
    return re.sub(r'[^a-z0-9]+', ' ', text).strip()


def _language(n, tokens):
    compact = n.replace(' ', '')
    has_cz = bool(tokens & _CZ) or 'czaudio' in compact
    has_sk = bool(tokens & _SK)
    dub = bool(_DUB.search(n)) or 'czaudio' in compact
    sub = bool(_SUB.search(n))
    if has_cz:
        if dub:
            return 'CZ_DUB'
        return 'CZ_SUB' if sub else 'CZ'
    if has_sk:
        return 'SK_SUB' if sub and not dub else 'SK_DUB'
    if tokens & _EN:
        return 'EN'
    return 'OTHER'


def _quality(n, tokens, ext=''):
    # also "1080.p", "1080pHD", bare "2160"/"1080"
    if re.search(r'\b2160|\b(4k|uhd|uhdr|uhdtv)\b', n):
        return '4K'
    if re.search(r'\b1080|\b(fhd|fullhd|full hd)\b', n):
        return '1080p'
    if re.search(r'\b720 ?p', n) or (tokens & {'hd', 'hdtv'} and not tokens & {'xvid', 'divx'} and ext != 'avi'):
        return '720p'
    if re.search(r'\b(480|576) ?p', n):
        return '480p'
    if re.search(r'\b360 ?p', n):
        return '360p'
    return 'SD'


def _polish(tokens, ext):
    """Tie-breaker within the same language and quality: modern container, progressive, not a TV/cam rip."""
    score = 1 if ext in ('mkv', 'mp4') else 0
    if tokens & {'tvrip', 'dvb', '1080i', 'hdtv', 'satrip'}:
        score -= 1
    if tokens & {'cam', 'camrip', 'ts', 'telesync', 'hdcam', 'tc'}:
        score -= 3
    return score


def parse(name, size=0):
    """Everything the ranking and the chips need to know about one file name."""
    n = norm(name)
    tokens = set(n.split())
    ext = name.rsplit('.', 1)[-1].lower() if '.' in (name or '') else ''
    season = episode = se_pos = se_end = None
    for pattern in _SE_PATTERNS:
        match = pattern.search(n)
        if match:
            season, episode, se_pos, se_end = int(match.group(1)), int(match.group(2)), match.start(), match.end()
            break
    try:
        size = int(size or 0)
    except (TypeError, ValueError):
        size = 0
    return {'norm': n, 'season': season, 'episode': episode, 'head': _head(n, se_pos),
            'tail': _head(n[se_end:], None) if se_end is not None else '',
            'years': [int(y) for y in re.findall(r'\b(19[2-9]\d|20[0-4]\d)\b', n)],
            'quality': _quality(n, tokens, ext), 'lang': _language(n, tokens),
            'remux': 'remux' in tokens, 'polish': _polish(tokens, ext), 'size': size, 'size_mb': size // (1024 * 1024),
            'format': ext.upper() if ext in ('mkv', 'mp4', 'avi', 'mov', 'wmv', 'ts', 'm4v') else ''}


def lang_chip(info):
    return LANG_CHIP.get(info['lang'], '')


def make_target(kind, titles, year='', season=None, episode=None):
    """kind: 'movie', 'episode' or 'any' (manual search: titles = [query])."""
    variants = []
    for title in titles:
        t = norm(title)
        for v in (t, t[4:] if t.startswith('the ') else ''):
            if v and v not in variants:
                variants.append(v)
    try:
        year = int(str(year)[:4]) if year else None
    except ValueError:
        year = None
    return {'kind': kind, 'titles': variants, 'year': year, 'season': season, 'episode': episode}


def _head(n, se_pos):
    """The title part of a name: text before SxxEyy, else before the first year/quality/language/source word."""
    words = (n[:se_pos] if se_pos is not None else n).split()
    for i, word in enumerate(words):
        if i and (word in _STOP or _YEAR.match(word)):
            words = words[:i]
            break
    while words and words[0].isdigit() and len(words[0]) <= 3:  # "01 pratele ..."
        words = words[1:]
    return ' '.join(words)


def _only_title(text, variants):
    """The text is one or more of the titles and nothing else (apart from filler and words of the titles)."""
    padded = ' ' + text + ' '
    found = False
    for v in sorted(variants, key=len, reverse=True):
        if ' ' + v + ' ' in padded:
            padded = padded.replace(' ' + v + ' ', ' ')
            found = True
    allowed = _FILLER.union(*(v.split() for v in variants)) if variants else _FILLER
    return found and all(w in allowed and not w.isdigit() for w in padded.split())


def _title_position(info, variants):
    """2 = the title part is only the title(s), 1 = title with other words or elsewhere, 0 = no title."""
    head = info['head']
    heads = [head]
    first = head.split(' ', 1)
    if len(first) == 2 and _YEAR.match(first[0]):
        heads.append(first[1])  # "1991 Terminator 2 ..."
    if any(_only_title(h, variants) for h in heads):
        return 2
    if not head and info['tail']:
        # "1x01 - Teorie velkého třesku - Pilot": number first, then the title (+ episode name)
        tail = ' ' + info['tail'] + ' '
        if any(tail.startswith(' ' + v + ' ') for v in variants):
            return 2
    if any(re.search(r'\b' + re.escape(v) + r'\b', info['norm']) for v in variants):
        return 1
    return 0


def match_level(info, target):
    """2 = the requested title, 1 = probably (title not at the start), 0 = something else."""
    title = _title_position(info, target['titles'])
    kind = target['kind']
    if kind == 'episode':
        if (info['season'], info['episode']) != (target['season'], target['episode']):
            return 0
        return 2 if title == 2 else 1
    if kind == 'movie':
        if info['season'] is not None or not title:
            return 0
        if target['year'] and info['years'] and not any(abs(y - target['year']) <= 1 for y in info['years']):
            return 0
        return title
    return 2 if title else 1


def _effective_quality(info, kind):
    floor = BELIEVABLE_MB['episode' if kind == 'episode' else 'movie'].get(info['quality'])
    if floor and info['size_mb'] and info['size_mb'] < floor:
        return 'SD'
    return info['quality']


def _quality_key(quality, preferred):
    """Preferred quality first, then lower ones (closest first), then higher ones."""
    q = QUALITIES.index(quality)
    if not preferred or preferred not in QUALITIES:
        return q
    p = QUALITIES.index(preferred)
    if q == p:
        return 20
    return 10 + q if q < p else -q


def _votes(item):
    try:
        return int(item.get('positive_votes') or 0) - int(item.get('negative_votes') or 0)
    except (TypeError, ValueError):
        return 0


def sort_key(item, target, preferred_quality=None):
    info = item['info']
    kind = target['kind']
    return (item['match'], LANG_RANK[info['lang']],
            _quality_key(_effective_quality(info, kind), preferred_quality),
            info['polish'], info['remux'], min(max(_votes(item), -3), 3), info['size'])


def rank(files, target, preferred_quality=None):
    """New list: files with 'info' and 'match' added, best first. Samples and files of any other
    movie/series/episode are dropped (manual search keeps all). Input is not modified."""
    strict = target['kind'] in ('movie', 'episode')
    min_mb = MIN_SIZE_MB['episode' if target['kind'] == 'episode' else 'movie']
    ranked = []
    seen = set()
    for item in files:
        key = item.get('ident') or item.get('name')
        if key in seen:
            continue
        seen.add(key)
        item = dict(item)
        item['info'] = parse(item.get('name', ''), item.get('size'))
        if item['info']['size_mb'] and item['info']['size_mb'] < min_mb:
            continue
        # KRA.sk names are synthetic (built from the requested title), Stream Cinema did the matching
        item['match'] = 2 if item.get('provider') == 'kraska' else match_level(item['info'], target)
        if strict and item['match'] < 2:
            continue
        ranked.append(item)
    ranked.sort(key=lambda it: (sort_key(it, target, preferred_quality), it.get('ident') or ''), reverse=True)
    return ranked


def pick(ranked):
    """What autoplay should play: the first row the dialog would show."""
    return ranked[0] if ranked else None


def episode_queries(titles, season, episode):
    queries = ['{} S{:02d}E{:02d}'.format(t, season, episode) for t in titles if t]
    if titles and titles[0]:
        queries.append('{} {}x{:02d}'.format(titles[0], season, episode))
    return list(dict.fromkeys(queries))


def movie_queries(titles, year):
    queries = ['{} {}'.format(t, year).strip() for t in titles if t]
    return list(dict.fromkeys(queries))
