# -*- coding: utf-8 -*-
"""TMDb metadata cache inside library.db (docs/plan/milionar-library/030 §3).

Keys: movie:<id>, tv:<id>, season:<id>:<n>, collection:<id>, plus the generic key space http:<sha256> that
wcs/metadata/TMDbCache.py uses for everything else (logos, aliases, search results, ČSFD pages). Readers get
(data, stale); a stale or missing entry never blocks on the network here, the
caller decides whether to queue a refresh.
"""
import json
import time

from wcs.library2 import store

DAY = 86400
TTL_MOVIE = 30 * DAY
TTL_COLLECTION = 30 * DAY
TTL_RUNNING = 12 * 3600
TTL_ENDED = 30 * DAY
RUNNING_STATUSES = ('Returning Series', 'In Production', 'Planned', 'Pilot')


def key_movie(tmdb_id):
    return 'movie:{}'.format(int(tmdb_id))


def key_tv(tmdb_id):
    return 'tv:{}'.format(int(tmdb_id))


def key_season(tmdb_id, season):
    return 'season:{}:{}'.format(int(tmdb_id), int(season))


def key_collection(collection_id):
    return 'collection:{}'.format(int(collection_id))


def ttl_for_status(status):
    """tv:<id> and its seasons: 12 h while the show is running, 30 days otherwise (030 §3)."""
    return TTL_RUNNING if status in RUNNING_STATUSES else TTL_ENDED


def put(key, data, ttl, now=None):
    put_many([(key, data, ttl)], now)


def _rows_for(key):
    from wcs.skin import refresh
    if key.startswith('movie:'):
        return refresh.media_rows('movie', membership=True)
    if key.startswith(('tv:', 'season:')):
        return refresh.media_rows('tv', membership=True) | {'widget_mytv_next'}
    return set()


def put_many(entries, now=None):
    """Commit a complete metadata snapshot together; unchanged content only renews TTL."""
    entries = list(entries)
    if not entries:
        return
    fetched = int(now if now is not None else time.time())
    with store.tx() as conn:
        for key, data, ttl in entries:
            encoded = json.dumps(data, ensure_ascii=False, sort_keys=True, separators=(',', ':'))
            old = conn.execute('SELECT json FROM meta WHERE key=?', (key,)).fetchone()
            changed = old is None or json.loads(old['json']) != data
            conn.execute('INSERT OR REPLACE INTO meta(key, json, fetched_at, ttl) VALUES(?,?,?,?)',
                         (key, encoded, fetched, int(ttl)))
            if changed:
                store.dirty(conn, _rows_for(key))


def get(key, now=None):
    """(data, stale) or (None, True) when the key was never cached."""
    conn = store.connect()
    try:
        row = conn.execute('SELECT json, fetched_at, ttl FROM meta WHERE key=?', (key,)).fetchone()
    finally:
        conn.close()
    if not row:
        return None, True
    stale = (int(now if now is not None else time.time()) - row['fetched_at']) >= row['ttl']
    return json.loads(row['json']), stale


def get_many(keys):
    """{key: data} of the cached keys (one connection; stale entries are served, absent keys are missing)."""
    return {key: value[0] for key, value in get_many_status(keys).items()}


def get_many_status(keys, now=None):
    """Bulk metadata and freshness, including expired snapshots."""
    keys = list(keys)
    if not keys:
        return {}
    conn = store.connect()
    try:
        found = {}
        for start in range(0, len(keys), 500):
            chunk = keys[start:start + 500]
            rows = conn.execute('SELECT key,json,fetched_at,ttl FROM meta WHERE key IN ({})'.format(','.join('?' * len(chunk))), chunk)
            stamp = int(now if now is not None else time.time())
            found.update({r['key']: (json.loads(r['json']), stamp - r['fetched_at'] >= r['ttl']) for r in rows})
    finally:
        conn.close()
    return found


def delete(key):
    with store.tx() as conn:
        if conn.execute('DELETE FROM meta WHERE key=?', (key,)).rowcount:
            store.dirty(conn, _rows_for(key))


def stale_keys(now=None):
    now = int(now if now is not None else time.time())
    conn = store.connect()
    try:
        rows = conn.execute('SELECT key FROM meta WHERE ? - fetched_at >= ttl', (now,)).fetchall()
    finally:
        conn.close()
    return [r['key'] for r in rows]


def get_aged(key, now=None):
    """(data, age_seconds) or None; for callers that pick the lifetime at read time (generic key space)."""
    conn = store.connect()
    try:
        row = conn.execute('SELECT json, fetched_at FROM meta WHERE key=?', (key,)).fetchone()
    finally:
        conn.close()
    if not row:
        return None
    return json.loads(row['json']), int(now if now is not None else time.time()) - row['fetched_at']


def items_with_prefix(prefix):
    """[(key, data)] of every entry whose key starts with `prefix`."""
    conn = store.connect()
    try:
        rows = conn.execute('SELECT key, json FROM meta WHERE substr(key, 1, ?) = ?', (len(prefix), prefix)).fetchall()
    finally:
        conn.close()
    return [(r['key'], json.loads(r['json'])) for r in rows]


def delete_keys(keys):
    keys = list(keys)
    if keys:
        with store.tx() as conn:
            for key in keys:
                if conn.execute('DELETE FROM meta WHERE key=?', (key,)).rowcount:
                    store.dirty(conn, _rows_for(key))
    return len(keys)
