# -*- coding: utf-8 -*-
"""TMDb data for the library through the meta cache (docs/plan/milionar-library/030 §3).

Reads serve a stale entry; only a never-seen key is fetched inline (the user explicitly started a play or opened a
listing). Network code is imported lazily so the offline tests never touch it.
"""
import xbmc

from wcs.library2 import meta

POSTER = 'https://image.tmdb.org/t/p/w500'
BACKDROP = 'https://image.tmdb.org/t/p/w1280'


def _fetch(path, params=None):
    import xbmcaddon
    from wcs.metadata import TMDbClient
    addon = xbmcaddon.Addon('plugin.video.milionar')
    params = dict(params or {})
    media, _slash, rest = path.partition('/')
    data = TMDbClient.fetch_tmdb_item_detail(media, rest, params, addon)
    return data or None


def _cached(key, path, ttl_of, params=None, fetch=None, complete=None, strict=False):
    """Cached TMDb data; an entry that lacks what `complete(data)` demands counts as missing (and is refetched).

    `strict` (listings the user opened) raises on a failed download so the caller can show the usual TMDb error
    dialog; otherwise failures give None (playback and background code cope with it).
    """
    data, _stale = meta.get(key)
    if data is not None and (complete is None or complete(data)):
        return data
    try:
        data = (fetch or _fetch)(path, params)
        if strict and not data:
            raise RuntimeError('TMDb nevrátilo data ({})'.format(path))
    except Exception as exc:  # no key / offline: callers cope with None
        if strict:
            raise
        xbmc.log('[Milionář Library] TMDb {} failed: {}: {}'.format(path, type(exc).__name__, exc), xbmc.LOGWARNING)
        return None
    if data:
        meta.put(key, data, ttl_of(data))
    return data


def movie(tmdb_id, fetch=None):
    return _cached(meta.key_movie(tmdb_id), 'movie/{}'.format(int(tmdb_id)), lambda d: meta.TTL_MOVIE,
                   {'append_to_response': 'credits'}, fetch, lambda d: 'genres' in d)


def show(tmdb_id, fetch=None, strict=False):
    return _cached(meta.key_tv(tmdb_id), 'tv/{}'.format(int(tmdb_id)),
                   lambda d: meta.ttl_for_status(d.get('status')), None, fetch, lambda d: 'seasons' in d, strict)


def detail(media, tmdb_id, fetch=None):
    """Full skin detail in the same canonical cache as playback; rows never fetch it."""
    key = meta.key_movie(tmdb_id) if media == 'movie' else meta.key_tv(tmdb_id)
    fields = ('credits', 'videos', 'recommendations', 'similar')
    return _cached(key, '{}/{}'.format(media, int(tmdb_id)),
                   lambda d: meta.TTL_MOVIE if media == 'movie' else meta.ttl_for_status(d.get('status')),
                   {'append_to_response': ','.join(fields)}, fetch,
                   lambda d: all(isinstance(d.get(field), dict) for field in fields), strict=True)


def season(tmdb_id, number, fetch=None, strict=False):
    show_data = meta.get(meta.key_tv(tmdb_id))[0] or {}
    return _cached(meta.key_season(tmdb_id, number), 'tv/{}/season/{}'.format(int(tmdb_id), int(number)),
                   lambda d: meta.ttl_for_status(show_data.get('status')), None, fetch, lambda d: 'episodes' in d, strict)


def refresh_show(tmdb_id, fetch=None):
    """Force a fresh `tv:<id>` and its regular seasons into the cache (service refresher). Returns keys written."""
    fetch = fetch or _fetch
    data = fetch('tv/{}'.format(int(tmdb_id)), None)
    if not isinstance(data, dict) or not isinstance(data.get('seasons'), list):
        raise RuntimeError('Incomplete series metadata')
    ttl = meta.ttl_for_status(data.get('status'))
    entries = [(meta.key_tv(tmdb_id), data, ttl)]
    for entry in data.get('seasons') or []:
        number = int(entry.get('season_number', 0))
        if number <= 0:
            continue
        from wcs.skin import userfirst
        userfirst.breathe()                           # the service's refresh steps aside for the user between seasons
        season_data = fetch('tv/{}/season/{}'.format(int(tmdb_id), number), None)
        if not isinstance(season_data, dict) or not isinstance(season_data.get('episodes'), list):
            raise RuntimeError('Incomplete season metadata')
        expected = int(entry.get('episode_count') or 0)
        if len(season_data['episodes']) < expected:
            raise RuntimeError('Incomplete episode metadata')
        entries.append((meta.key_season(tmdb_id, number), season_data, ttl))
    meta.put_many(entries)
    return [key for key, _data, _ttl in entries]


def refresh_movie(tmdb_id, fetch=None):
    data = (fetch or _fetch)('movie/{}'.format(int(tmdb_id)), {'append_to_response': 'credits'})
    if not isinstance(data, dict) or not isinstance(data.get('genres'), list):
        raise RuntimeError('Incomplete movie metadata')
    meta.put(meta.key_movie(tmdb_id), data, meta.TTL_MOVIE)
    return data


def collection(collection_id, fetch=None):
    return _cached(meta.key_collection(collection_id), 'collection/{}'.format(int(collection_id)),
                   lambda d: meta.TTL_COLLECTION, None, fetch, lambda d: 'parts' in d)


def movie_info(tmdb_id, fetch=None):
    """Flat dict the playing code needs: title, year, plot, poster, fanart, rating, genre, director, runtime (min)."""
    data = movie(tmdb_id, fetch)
    if not data:
        return None
    crew = ((data.get('credits') or {}).get('crew')) or []
    director = next((c.get('name', '') for c in crew if c.get('job') == 'Director'), '')
    date = data.get('release_date') or ''
    return {'title': data.get('title') or data.get('name') or '', 'year': date[:4], 'plot': data.get('overview', ''),
            'poster': POSTER + data['poster_path'] if data.get('poster_path') else (data.get('poster') or ''),
            'fanart': BACKDROP + data['backdrop_path'] if data.get('backdrop_path') else '',
            'rating': '{:.1f}'.format(data['vote_average']) if data.get('vote_average') else '',
            'genre': ', '.join(g.get('name', '') for g in data.get('genres') or []), 'director': director,
            'runtime': str(data.get('runtime') or '')}


def episode_info(show_id, season_number, episode_number, fetch=None):
    """Flat dict for one episode (plus show fields); None when the show is unknown."""
    show_data = show(show_id, fetch)
    if not show_data:
        return None
    season_data = season(show_id, season_number, fetch) or {}
    episode = next((e for e in season_data.get('episodes') or [] if e.get('episode_number') == int(episode_number)), {})
    date = show_data.get('first_air_date') or ''
    return {'series_name': show_data.get('name', ''), 'year': date[:4],
            'plot': episode.get('overview') or show_data.get('overview', ''),
            'poster': POSTER + show_data['poster_path'] if show_data.get('poster_path') else '',
            'fanart': BACKDROP + show_data['backdrop_path'] if show_data.get('backdrop_path') else '',
            'rating': '{:.1f}'.format(show_data['vote_average']) if show_data.get('vote_average') else '',
            'genre': ', '.join(g.get('name', '') for g in show_data.get('genres') or []),
            'episode_title': episode.get('name') or 'Epizoda {}'.format(episode_number),
            'episode_thumb': POSTER + episode['still_path'] if episode.get('still_path') else '',
            'runtime': str(episode.get('runtime') or (show_data.get('episode_run_time') or [''])[0] or ''),
            'air_date': episode.get('air_date', ''), 'tmdb_id': str(show_id)}
