# -*- coding: utf-8 -*-
"""Cards and taste features of TMDb titles (docs/plan/pro-tebe/010 §3). Pure: no Kodi, no network."""

# TV genres folded onto the movie genre ids, so a series and a film share their taste features.
TV_GENRES = {10759: (28, 12), 10765: (878, 14), 10768: (10752,), 10762: (10751,)}
# Genres a household rarely wants unless its taste shows them (penalised, not removed).
NICHE_GENRES = {16, 99, 10402, 10762, 10763, 10764, 10766, 10767, 10770}
# Moods a household has or has not: penalised when the taste does not show them at all.
MOOD_GENRES = {27, 37, 10752, 80}
# Never recommended: news, reality, soap, talk shows.
BLOCKED_TV_GENRES = {10763, 10764, 10766, 10767}

# US certification -> audience level: 0 kids, 1 family, 2 teen, 3 adult (DE ages as the fallback)
CERTS = {'TV-Y': 0, 'TV-Y7': 0, 'TV-Y7-FV': 0, 'TV-G': 1, 'G': 0, 'TV-PG': 1, 'PG': 1, 'TV-14': 2, 'PG-13': 2,
         'TV-MA': 3, 'R': 3, 'NC-17': 3}
DE_AGES = {'0': 0, '6': 1, '12': 2, '16': 3, '18': 3}
WEIGHTS = {'cert': 0.6, 'co': 0.4, 'net': 0.5, 'oc': 0.45, 'g': 1.0, 'k': 0.5, 'c': 0.45, 'd': 0.9, 'col': 1.2, 'lang': 0.35, 'dec': 0.25, 'm': 0.2}
CAST = 5
KEYWORDS = 15


def key(media, tmdb_id):
    return '{}:{}'.format(media, int(tmdb_id))


def genre_ids(data):
    ids = data.get('genre_ids')
    if ids is None:
        ids = [g.get('id') for g in data.get('genres') or [] if g.get('id')]
    return [int(i) for i in ids]


def folded_genres(ids):
    out = []
    for gid in ids:
        for mapped in TV_GENRES.get(gid, (gid,)):
            if mapped not in out:
                out.append(mapped)
    return out


def year_of(data):
    date = data.get('release_date') or data.get('first_air_date') or ''
    return int(date[:4]) if date[:4].isdigit() else 0


def audience(data, media):
    """0 kids … 3 adult, -1 unknown, from TMDb content_ratings (series) / release_dates (films)."""
    def pick(codes, table):
        levels = [table[c] for c in codes if c in table]
        return max(levels) if levels else None
    if media == 'tv':
        ratings = {}
        for r in (data.get('content_ratings') or {}).get('results') or []:
            ratings.setdefault(r.get('iso_3166_1'), []).append(r.get('rating') or '')
        found = pick(ratings.get('US', []), CERTS)
        if found is None:
            found = pick(ratings.get('DE', []), DE_AGES)
    else:
        certs = {}
        for r in (data.get('release_dates') or {}).get('results') or []:
            certs[r.get('iso_3166_1')] = [d.get('certification') or '' for d in r.get('release_dates') or []]
        found = pick(certs.get('US', []), CERTS)
        if found is None:
            found = pick(certs.get('DE', []), DE_AGES)
    return -1 if found is None else found


def card(data, media):
    """Everything a row, the hero and the scorer need from a list item or a detail (detail adds runtime etc.)."""
    runtime = data.get('runtime')
    if runtime is None and media == 'tv':
        runtime = (data.get('episode_run_time') or [None])[0] or (data.get('last_episode_to_air') or {}).get('runtime')
    collection = data.get('belongs_to_collection') or {}
    return {
        'media': media, 'id': int(data['id']),
        'title': data.get('title') or data.get('name') or '',
        'original_title': data.get('original_title') or data.get('original_name') or '',
        'year': year_of(data), 'date': data.get('release_date') or data.get('first_air_date') or '',
        'overview': data.get('overview') or '', 'poster': data.get('poster_path') or '',
        'backdrop': data.get('backdrop_path') or '',
        'rating': float(data.get('vote_average') or 0), 'votes': int(data.get('vote_count') or 0),
        'popularity': float(data.get('popularity') or 0), 'genres': genre_ids(data),
        'language': data.get('original_language') or '', 'runtime': int(runtime or 0),
        'collection': int(collection['id']) if collection.get('id') else 0,
        'seasons': int(data.get('number_of_seasons') or 0), 'adult': bool(data.get('adult')),
        'status': data.get('status') or '', 'audience': audience(data, media),
        'end_year': (data.get('last_air_date') or '')[:4] if media == 'tv' else '',
        'ended': (data.get('status') in ('Ended', 'Canceled')) if media == 'tv' and data.get('status') else None,
    }


def _people(data, media):
    credits = data.get('credits') or {}
    cast = [p for p in credits.get('cast') or [] if p.get('id')][:CAST]
    makers = [p for p in credits.get('crew') or [] if p.get('job') in ('Director',) and p.get('id')]
    if media == 'tv':
        makers = [p for p in data.get('created_by') or [] if p.get('id')] or makers
    return cast, makers[:3]


def _keywords(data):
    block = data.get('keywords') or {}
    return [k for k in (block.get('keywords') or block.get('results') or []) if k.get('id')][:KEYWORDS]


def features(data, media):
    """Sparse feature vector {name: weight}; a list item gives genres, language, decade, a detail adds the rest."""
    out = {}
    for gid in folded_genres(genre_ids(data)):
        out['g:{}'.format(gid)] = WEIGHTS['g']
    lang = data.get('original_language')
    if lang:
        out['lang:' + lang] = WEIGHTS['lang']
    if media == 'movie':
        for company in (data.get('production_companies') or [])[:2]:
            if company.get('id'):
                out['co:{}'.format(company['id'])] = WEIGHTS['co']   # Pixar, A24, Marvel: the studio's tone
    for network in (data.get('networks') or [])[:2]:
        if network.get('id'):
            out['net:{}'.format(network['id'])] = WEIGHTS['net']   # BBC vs. Nickelodeon: the tone of a series
    for country in (data.get('origin_country') or [])[:2]:
        out['oc:' + country] = WEIGHTS['oc']       # British vs. American comedy is a matter of taste
    year = year_of(data)
    if year:
        out['dec:{}'.format(year // 10 * 10)] = WEIGHTS['dec']
    out['m:' + media] = WEIGHTS['m']
    level = audience(data, media)
    if level >= 0:
        out['cert:{}'.format(level)] = WEIGHTS['cert']   # Krok za krokem (TV-G) is not Shameless (TV-MA)
    for kw in _keywords(data):
        out['k:{}'.format(kw['id'])] = WEIGHTS['k']
    cast, makers = _people(data, media)
    for rank, person in enumerate(cast):
        out['c:{}'.format(person['id'])] = WEIGHTS['c'] * (1.0 - 0.1 * rank)
    for person in makers:
        out['d:{}'.format(person['id'])] = WEIGHTS['d']
    collection = data.get('belongs_to_collection') or {}
    if collection.get('id'):
        out['col:{}'.format(collection['id'])] = WEIGHTS['col']
    return out


def names(data, media):
    """Readable names of the people / keywords / collection features, for the reason texts."""
    out = {}
    cast, makers = _people(data, media)
    for person in cast + makers:
        prefix = 'd:' if person in makers else 'c:'
        out['{}{}'.format(prefix, person['id'])] = person.get('name') or ''
    for kw in _keywords(data):
        out['k:{}'.format(kw['id'])] = kw.get('name') or ''
    collection = data.get('belongs_to_collection') or {}
    if collection.get('id'):
        out['col:{}'.format(collection['id'])] = collection.get('name') or ''
    return out


# keywords that say nothing about taste (TMDb tags them on everything)
STOP_KEYWORDS = {818, 9663, 179431, 179430, 9717, 18712, 9672, 6054, 9713, 155477}
# based on novel / comic / graphic novel / true story, sequel, (during|after)creditsstinger, friendship, friends, softcore


def seed_keywords(data, limit=4):
    """The seed's own keywords that are worth a discover query."""
    return [k['id'] for k in _keywords(data) if k['id'] not in STOP_KEYWORDS][:limit]


def is_detail(data):
    return isinstance(data, dict) and 'credits' in data and 'keywords' in data
