# wcs/games/demo/catalog.py
"""The demo titles: loading and validation of resources/demo/millionaire/titles/*.json.

A file holds one film or series with its nine questions (3 easy, 3 medium, 3 hard). Pure Python, no Kodi imports, so
the data can be checked offline (tests/test_millionaire_demo.py).
"""
import json
import os
from functools import lru_cache
from typing import NamedTuple, Tuple

ROUNDS = 9
OPTIONS = 4
MEDIA = ('movie', 'series')
MAX_QUESTION, MAX_OPTION, MAX_EXPLANATION, MAX_TIP, MAX_LINE = 140, 30, 260, 260, 90

PACK_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', '..', 'resources', 'demo', 'millionaire')


class Question(NamedTuple):
    text: str
    options: Tuple[str, ...]
    correct: int
    explanation: str
    tip: str


class DemoTitle(NamedTuple):
    id: str
    media: str                         # movie | series
    title: str
    year: int
    tmdb_id: int
    genre: str
    plot: str
    tagline: str                       # the question of the title screen ("Jak dobře znáte ...?")
    win_line: str                      # the praise after a perfect game
    poster: str                        # TMDb poster_path ("/abc.jpg")
    fanart: str                        # TMDb backdrop_path
    questions: Tuple[Question, ...]


def _text_problems(label, value, limit):
    if not isinstance(value, str) or not value.strip():
        return ['{}: missing text'.format(label)]
    return ['{}: {} chars (max {})'.format(label, len(value), limit)] if len(value) > limit else []


def problems(raw):
    """Everything wrong with one parsed title file (empty list: valid)."""
    found = []
    for key in ('id', 'title', 'genre', 'plot', 'poster', 'fanart'):
        found += _text_problems(key, raw.get(key), 400)
    for key in ('tagline', 'win_line'):
        found += _text_problems(key, raw.get(key), MAX_LINE)
    if raw.get('media') not in MEDIA:
        found.append('media: {!r}'.format(raw.get('media')))
    for key in ('year', 'tmdb_id', 'order'):
        if not isinstance(raw.get(key), int):
            found.append('{}: not a number'.format(key))
    questions = raw.get('questions')
    if not isinstance(questions, list) or len(questions) != ROUNDS:
        return found + ['questions: need exactly {}'.format(ROUNDS)]
    seen = set()
    for number, item in enumerate(questions, 1):
        label = 'q{}'.format(number)
        found += _text_problems(label + '.question', item.get('question'), MAX_QUESTION)
        found += _text_problems(label + '.explanation', item.get('explanation'), MAX_EXPLANATION)
        found += _text_problems(label + '.tip', item.get('tip'), MAX_TIP)
        options = item.get('options')
        if not isinstance(options, list) or len(options) != OPTIONS:
            found.append(label + '.options: need exactly {}'.format(OPTIONS))
        else:
            for letter, option in zip('ABCD', options):
                found += _text_problems('{}.{}'.format(label, letter), option, MAX_OPTION)
            if len({str(o).strip().lower() for o in options}) != OPTIONS:
                found.append(label + '.options: duplicates')
        if item.get('correct') not in range(OPTIONS):
            found.append(label + '.correct: {!r}'.format(item.get('correct')))
        if item.get('question') in seen:
            found.append(label + ': repeated question')
        seen.add(item.get('question'))
    if len({item.get('correct') for item in questions}) < 3:
        found.append('correct: the right answer sits on fewer than 3 different letters')
    return found


def parse(raw):
    """DemoTitle of a valid file; ValueError listing the problems otherwise."""
    issues = problems(raw)
    if issues:
        raise ValueError('{}: {}'.format(raw.get('id', '?'), '; '.join(issues)))
    questions = tuple(Question(q['question'], tuple(q['options']), q['correct'], q['explanation'], q['tip'])
                      for q in raw['questions'])
    return DemoTitle(raw['id'], raw['media'], raw['title'], raw['year'], raw['tmdb_id'], raw['genre'], raw['plot'],
                     raw['tagline'], raw['win_line'], raw['poster'], raw['fanart'], questions)


def read_json(*parts):
    with open(os.path.join(PACK_DIR, *parts), encoding='utf-8') as handle:
        return json.load(handle)


@lru_cache(maxsize=1)
def titles():
    """All valid demo titles, movies first, each group in its `order`. A broken file is skipped, never fatal."""
    folder = os.path.join(PACK_DIR, 'titles')
    loaded = []
    for name in sorted(os.listdir(folder)) if os.path.isdir(folder) else ():
        if not name.endswith('.json'):
            continue
        try:
            raw = read_json('titles', name)
            loaded.append((MEDIA.index(raw['media']) if raw.get('media') in MEDIA else len(MEDIA),
                           raw.get('order', 0), parse(raw)))
        except (OSError, ValueError, KeyError) as error:
            print('[Milionář Demo] {} skipped: {}'.format(name, error))
    return tuple(title for _media, _order, title in sorted(loaded, key=lambda entry: entry[:2]))


def by_media(media):
    return [title for title in titles() if title.media == media]


def get(title_id):
    """The demo title with this id, or None."""
    return next((title for title in titles() if title.id == title_id), None)
