"""Persistent search and watch history in the addon profile."""
from __future__ import annotations

import json
import os
import time
from typing import Any

import xbmcvfs
from xbmcaddon import Addon

from .constants import ADDON_ID, SEARCH_HISTORY_MAX, WATCH_HISTORY_MAX


def _addon_profile_dir(addon: Addon) -> str:
    """Ensure addon profile exists and return a filesystem path for file I/O."""
    profile = addon.getAddonInfo("profile")
    if profile and not xbmcvfs.exists(profile):
        xbmcvfs.mkdirs(profile)
    return xbmcvfs.translatePath(profile)


class _JsonListStore:
    def __init__(self, addon: Addon, filename: str, limit: int) -> None:
        self._addon = addon
        profile = _addon_profile_dir(addon)
        self._path = os.path.join(profile, filename)
        self._limit = limit

    def _load(self) -> list[Any]:
        if not os.path.isfile(self._path):
            return []
        try:
            with open(self._path, encoding="utf-8") as fh:
                data = json.load(fh)
            return data if isinstance(data, list) else []
        except (OSError, json.JSONDecodeError):
            return []

    def _save(self, items: list[Any]) -> None:
        with open(self._path, "w", encoding="utf-8") as fh:
            json.dump(items[: self._limit], fh, ensure_ascii=False, indent=2)


class SearchHistoryStore(_JsonListStore):
    def __init__(self, addon: Addon | None = None, filename: str = "search_history.json") -> None:
        super().__init__(addon or Addon(ADDON_ID), filename, SEARCH_HISTORY_MAX)

    def list(self) -> list[str]:
        return [q for q in self._load() if isinstance(q, str) and q.strip()]

    def touch(self, query: str) -> None:
        query = query.strip()
        if not query:
            return
        items = [q for q in self.list() if q != query]
        items.insert(0, query)
        self._save(items)

    def remove(self, query: str) -> None:
        query = query.strip()
        self._save([q for q in self.list() if q != query])


class WatchHistoryStore(_JsonListStore):
    def __init__(self, addon: Addon | None = None) -> None:
        super().__init__(addon or Addon(ADDON_ID), "watch_history.json", WATCH_HISTORY_MAX)

    @staticmethod
    def _key(channel: str, start: str, stop: str) -> tuple[str, str, str]:
        return channel, start, stop

    def list(self) -> list[dict[str, Any]]:
        return [entry for entry in self._load() if isinstance(entry, dict)]

    def upsert(self, entry: dict[str, Any]) -> None:
        channel = entry.get("channel", "")
        start = entry.get("start", "")
        stop = entry.get("stop", "")
        if not channel or not start or not stop:
            return

        key = self._key(channel, start, stop)
        items = [
            e
            for e in self.list()
            if self._key(e.get("channel", ""), e.get("start", ""), e.get("stop", "")) != key
        ]
        items.insert(
            0,
            {
                "channel": channel,
                "start": start,
                "stop": stop,
                "title": entry.get("title", ""),
                "series_title": entry.get("series_title", ""),
                "station_name": entry.get("station_name", ""),
                "watched_at": time.time(),
            },
        )
        self._save(items)

    def get(self, channel: str, start: str, stop: str) -> dict[str, Any]:
        key = self._key(channel, start, stop)
        for entry in self.list():
            if self._key(
                str(entry.get("channel") or ""),
                str(entry.get("start") or ""),
                str(entry.get("stop") or ""),
            ) == key:
                return entry
        return {}

    def remove(self, channel: str, start: str, stop: str) -> None:
        key = self._key(channel, start, stop)
        self._save(
            [
                e
                for e in self.list()
                if self._key(e.get("channel", ""), e.get("start", ""), e.get("stop", "")) != key
            ]
        )


class VodWatchHistoryStore(_JsonListStore):
    def __init__(self, addon: Addon | None = None) -> None:
        super().__init__(addon or Addon(ADDON_ID), "vod_watch_history.json", WATCH_HISTORY_MAX)

    @staticmethod
    def _key(movie_id: int) -> int:
        return movie_id

    def list(self) -> list[dict[str, Any]]:
        return [entry for entry in self._load() if isinstance(entry, dict)]

    def upsert(self, entry: dict[str, Any]) -> None:
        movie_id = entry.get("movie_id")
        try:
            movie_key = int(movie_id)
        except (TypeError, ValueError):
            return
        if movie_key <= 0:
            return

        title = str(entry.get("title") or "").strip()
        if not title:
            return

        items = [e for e in self.list() if self._key(int(e.get("movie_id") or 0)) != movie_key]
        items.insert(
            0,
            {
                "movie_id": movie_key,
                "title": title,
                "display_title": str(entry.get("display_title") or title).strip(),
                "series_title": str(entry.get("series_title") or "").strip(),
                "meta": str(entry.get("meta") or "").strip(),
                "year": str(entry.get("year") or "").strip(),
                "watched_at": time.time(),
            },
        )
        self._save(items)

    def get(self, movie_id: int) -> dict[str, Any]:
        key = self._key(movie_id)
        for entry in self.list():
            try:
                if self._key(int(entry.get("movie_id") or 0)) == key:
                    return entry
            except (TypeError, ValueError):
                continue
        return {}

    def remove(self, movie_id: int) -> None:
        key = self._key(movie_id)
        self._save(
            [
                e
                for e in self.list()
                if self._key(int(e.get("movie_id") or 0)) != key
            ]
        )


class ProgrammeMetaStore:
    """Display metadata keyed by programme identity (not stored in play URL)."""

    def __init__(self, addon: Addon | None = None) -> None:
        addon = addon or Addon(ADDON_ID)
        profile = _addon_profile_dir(addon)
        self._path = os.path.join(profile, "programme_meta.json")
        self._cache: dict[str, Any] | None = None
        self._dirty = False

    @staticmethod
    def _key(channel: str, start: str, stop: str) -> str:
        return f"{channel}|{start}|{stop}"

    def _load(self) -> dict[str, Any]:
        if not os.path.isfile(self._path):
            return {}
        try:
            with open(self._path, encoding="utf-8") as fh:
                data = json.load(fh)
            return data if isinstance(data, dict) else {}
        except (OSError, json.JSONDecodeError):
            return {}

    def _save(self, data: dict[str, Any]) -> None:
        with open(self._path, "w", encoding="utf-8") as fh:
            json.dump(data, fh, ensure_ascii=False, indent=2)

    def _data(self) -> dict[str, Any]:
        if self._cache is None:
            self._cache = self._load()
        return self._cache

    def remember(self, channel: str, start: str, stop: str, **meta: Any) -> None:
        if not channel or not start or not stop:
            return
        entry = {k: str(v) for k, v in meta.items() if v is not None and str(v).strip()}
        if entry:
            self._data()[self._key(channel, start, stop)] = entry
            self._dirty = True

    def get(self, channel: str, start: str, stop: str) -> dict[str, str]:
        entry = self._data().get(self._key(channel, start, stop), {})
        return entry if isinstance(entry, dict) else {}

    def flush(self) -> None:
        if not self._dirty or self._cache is None:
            return
        self._save(self._cache)
        self._dirty = False


class VodItemMetaStore:
    """Display metadata keyed by VOD movie id (not stored in play URL)."""

    def __init__(self, addon: Addon | None = None) -> None:
        addon = addon or Addon(ADDON_ID)
        profile = _addon_profile_dir(addon)
        self._path = os.path.join(profile, "vod_item_meta.json")
        self._cache: dict[str, Any] | None = None
        self._dirty = False

    @staticmethod
    def _key(movie_id: int) -> str:
        return str(movie_id)

    def _load(self) -> dict[str, Any]:
        if not os.path.isfile(self._path):
            return {}
        try:
            with open(self._path, encoding="utf-8") as fh:
                data = json.load(fh)
            return data if isinstance(data, dict) else {}
        except (OSError, json.JSONDecodeError):
            return {}

    def _save(self, data: dict[str, Any]) -> None:
        with open(self._path, "w", encoding="utf-8") as fh:
            json.dump(data, fh, ensure_ascii=False, indent=2)

    def _data(self) -> dict[str, Any]:
        if self._cache is None:
            self._cache = self._load()
        return self._cache

    def remember(self, movie_id: int, **meta: Any) -> None:
        if movie_id <= 0:
            return
        entry = {k: str(v) for k, v in meta.items() if v is not None and str(v).strip()}
        if entry:
            self._data()[self._key(movie_id)] = entry
            self._dirty = True

    def get(self, movie_id: int) -> dict[str, str]:
        entry = self._data().get(self._key(movie_id), {})
        return entry if isinstance(entry, dict) else {}

    def flush(self) -> None:
        if not self._dirty or self._cache is None:
            return
        self._save(self._cache)
        self._dirty = False
