"""HTTP client for Antik middleware REST API."""
from __future__ import annotations

import base64
import json
import urllib.error
import urllib.parse
import urllib.request
from typing import Any

from xbmcaddon import Addon

from .constants import ADDON_ID
from . import logger
from .kodi_platform import hls_uses_pipe_auth
from .utils import archive_days_from_channel


class AntikApi:
    def __init__(self) -> None:
        self._addon = Addon(ADDON_ID)
        try:
            raw = self._addon.getSetting("middleware_url") or ""
            self._username = self._addon.getSetting("username") or ""
            self._password = self._addon.getSetting("password") or ""
        except RuntimeError:
            raw = ""
            self._username = ""
            self._password = ""
        self._base = raw.strip().rstrip("/")
        self._channels_cache: list[dict[str, Any]] | None = None
        if not self._base:
            message = self._addon.getLocalizedString(30027)
            raise ValueError(message if message and not message.isdigit() else (
                "Middleware URL is not configured. Open add-on settings and set the server address."
            ))

    def _add_auth(self, req: urllib.request.Request) -> None:
        if self._username and self._password:
            auth_str = f"{self._username}:{self._password}"
            b64_auth = base64.b64encode(auth_str.encode("utf-8")).decode("ascii")
            req.add_header("Authorization", f"Basic {b64_auth}")

    def _basic_auth_b64(self) -> str:
        if not self._username or not self._password:
            return ""
        auth_str = f"{self._username}:{self._password}"
        return base64.b64encode(auth_str.encode("utf-8")).decode("ascii")

    def _get_auth_suffix(self) -> str:
        b64_auth = self._basic_auth_b64()
        if b64_auth:
            return f"|Authorization=Basic%20{b64_auth}"
        return ""

    def inputstream_auth_headers(self) -> str:
        """HTTP headers for inputstream.adaptive (manifest + stream requests)."""
        b64_auth = self._basic_auth_b64()
        if not b64_auth:
            return ""
        # ISA wiki: header values must be URL-encoded; space in "Basic <token>" → %20
        return f"Authorization=Basic%20{b64_auth}"

    def hls_playlist_url(self, url: str) -> str:
        """Return playlist URL with platform-appropriate Basic Auth for Kodi playback."""
        clean = url.strip()
        if not clean or "|Authorization=" in clean:
            return clean
        if hls_uses_pipe_auth():
            return f"{clean}{self._get_auth_suffix()}"
        return clean

    def playback_auth(self) -> tuple[str, str]:
        """Headers for inputstream.adaptive and optional pipe-auth suffix for desktop."""
        headers = self.inputstream_auth_headers()
        suffix = self._get_auth_suffix() if hls_uses_pipe_auth() else ""
        return headers, suffix

    def authenticated_url(self, path_or_url: str) -> str:
        """Append Kodi pipe-auth for static assets (posters). Not for HLS playback."""
        url = path_or_url.strip()
        if not url:
            return url
        if not url.startswith("http://") and not url.startswith("https://"):
            url = self._url(url)
        suffix = self._get_auth_suffix()
        if not suffix or "|Authorization=" in url:
            return url
        return f"{url}{suffix}"

    @property
    def base_url(self) -> str:
        return self._base

    def _url(self, path: str) -> str:
        return f"{self._base}{path}"

    def get_json(self, path: str) -> Any:
        url = self._url(path)
        logger.debug(f"GET {url}")
        req = urllib.request.Request(url, method="GET")
        self._add_auth(req)
        try:
            with urllib.request.urlopen(req, timeout=30) as resp:
                data = json.loads(resp.read().decode("utf-8"))
                logger.debug(f"GET {path} -> {resp.status}")
                return data
        except urllib.error.HTTPError as exc:
            body = exc.read().decode("utf-8", errors="replace")[:200]
            logger.error(f"GET {path} failed: {exc.code} {body}")
            raise RuntimeError(f"HTTP {exc.code}: {path}") from exc
        except urllib.error.URLError as exc:
            logger.error(f"GET {path} failed: {exc.reason}")
            raise RuntimeError(f"Cannot reach middleware: {exc.reason}") from exc

    def post_json(self, path: str, body: dict[str, Any]) -> Any:
        url = self._url(path)
        payload = json.dumps(body).encode("utf-8")
        logger.debug(f"POST {url}")
        req = urllib.request.Request(
            url,
            data=payload,
            method="POST",
            headers={"Content-Type": "application/json"},
        )
        self._add_auth(req)
        with urllib.request.urlopen(req, timeout=30) as resp:
            data = json.loads(resp.read().decode("utf-8"))
            logger.debug(f"POST {path} -> {resp.status}")
            return data

    def auth_status(self) -> dict[str, Any]:
        return self.get_json("/api/v1/auth/status")

    def tv_channels(self, *, refresh: bool = False) -> list[dict[str, Any]]:
        if refresh or self._channels_cache is None:
            data = self.get_json("/api/v1/channels?type=tv")
            self._channels_cache = data if isinstance(data, list) else []
        return list(self._channels_cache)

    def archive_channels(self) -> list[dict[str, Any]]:
        return [
            ch
            for ch in self.tv_channels()
            if ch.get("archive") or archive_days_from_channel(ch) > 0
        ]

    def channel_by_id(self, channel_id: str) -> dict[str, Any] | None:
        needle = (channel_id or "").strip()
        if not needle:
            return None
        for ch in self.tv_channels():
            cid = str(ch.get("id_content") or ch.get("id") or "").strip()
            if cid == needle:
                return ch
        return None

    def archive_days_for_channel(self, channel_id: str, fallback: int = 0) -> int:
        ch = self.channel_by_id(channel_id)
        if ch is not None:
            days = archive_days_from_channel(ch)
            if days > 0:
                return days
        if fallback > 0:
            return min(fallback, 90)
        return 0

    def channel_name(self, channel_id: str) -> str:
        for ch in self.tv_channels():
            if ch.get("id_content") == channel_id or ch.get("id") == channel_id:
                return ch.get("name") or channel_id
        return channel_id

    def epg_for_day(self, channel_id: str, date: str) -> list[dict[str, Any]]:
        q = urllib.parse.urlencode({"date": date, "channel": channel_id})
        data = self.get_json(f"/api/v1/epg?{q}")
        if not isinstance(data, dict):
            return []
        progs = data.get(channel_id, [])
        return progs if isinstance(progs, list) else []

    def genres(self) -> list[dict[str, Any]]:
        return self.get_json("/api/v1/archive/genres").get("genres", [])

    def search(self, query: str, offset: int = 0, limit: int = 24) -> dict[str, Any]:
        params = {"q": query, "offset": offset, "limit": limit}
        q = urllib.parse.urlencode(params)
        return self.get_json(f"/api/v1/archive/search?{q}")

    def browse(self, genre: str | None = None, offset: int = 0, limit: int = 24) -> dict[str, Any]:
        params: dict[str, Any] = {"offset": offset, "limit": limit}
        if genre and genre != "all":
            params["genre"] = genre
        q = urllib.parse.urlencode(params)
        return self.get_json(f"/api/v1/archive/browse?{q}")

    def series(self, series_id: int, offset: int = 0, limit: int = 24) -> dict[str, Any]:
        q = urllib.parse.urlencode({"offset": offset, "limit": limit})
        return self.get_json(f"/api/v1/archive/series/{series_id}?{q}")

    def season(self, series_id: int, season: int, offset: int = 0, limit: int = 24) -> dict[str, Any]:
        q = urllib.parse.urlencode({"offset": offset, "limit": limit})
        return self.get_json(f"/api/v1/archive/season/{series_id}/{season}?{q}")

    def play_info(
        self,
        channel_id: str,
        start: str,
        stop: str,
        margin_before: int = 5,
        margin_after: int = 5,
    ) -> dict[str, Any]:
        q = urllib.parse.urlencode(
            {
                "channel_id": channel_id,
                "from": start,
                "to": stop,
                "margin_before": margin_before,
                "margin_after": margin_after,
            }
        )
        return self.get_json(f"/api/v1/archive/play-info?{q}")

    def archive_playlist_url(
        self,
        channel_id: str,
        start: str,
        stop: str,
        margin_before: int = 5,
        margin_after: int = 5,
        start_offset_ms: int | None = None,
    ) -> str:
        params: dict[str, str | int] = {
            "channel_id": channel_id,
            "from": start,
            "to": stop,
            "margin_before": margin_before,
            "margin_after": margin_after,
        }
        if start_offset_ms is not None:
            params["start_offset_ms"] = int(start_offset_ms)
        params["direct"] = "1"
        q = urllib.parse.urlencode(params)
        return f"{self._base}/stream/archive/playlist.m3u8?{q}"

    def vod_status(self) -> dict[str, Any]:
        return self.get_json("/api/v1/vod/status")

    def vod_genres(self) -> list[dict[str, Any]]:
        return self.get_json("/api/v1/vod/genres").get("genres", [])

    def vod_packages(self) -> list[dict[str, Any]]:
        return self.get_json("/api/v1/vod/packages").get("packages", [])

    def vod_package(self, package_id: int, offset: int = 0, limit: int = 24) -> dict[str, Any]:
        q = urllib.parse.urlencode({"offset": offset, "limit": limit})
        return self.get_json(f"/api/v1/vod/package/{package_id}?{q}")

    def vod_top(self, offset: int = 0, limit: int = 24) -> dict[str, Any]:
        q = urllib.parse.urlencode({"offset": offset, "limit": limit})
        return self.get_json(f"/api/v1/vod/top?{q}")

    def vod_browse(self, genre: int = 0, offset: int = 0, limit: int = 24) -> dict[str, Any]:
        q = urllib.parse.urlencode({"genre": genre, "offset": offset, "limit": limit})
        return self.get_json(f"/api/v1/vod/browse?{q}")

    def vod_search(self, query: str, offset: int = 0, limit: int = 24) -> dict[str, Any]:
        params = {"q": query, "offset": offset, "limit": limit}
        q = urllib.parse.urlencode(params)
        return self.get_json(f"/api/v1/vod/search?{q}")

    def vod_series(self, series_id: int, offset: int = 0, limit: int = 24) -> dict[str, Any]:
        q = urllib.parse.urlencode({"offset": offset, "limit": limit})
        return self.get_json(f"/api/v1/vod/series/{series_id}?{q}")

    def vod_season(self, series_id: int, season_id: int, offset: int = 0, limit: int = 24) -> dict[str, Any]:
        q = urllib.parse.urlencode({"offset": offset, "limit": limit})
        return self.get_json(f"/api/v1/vod/season/{series_id}/{season_id}?{q}")

    def vod_play_info(self, movie_id: int) -> dict[str, Any]:
        q = urllib.parse.urlencode({"movie_id": movie_id})
        return self.get_json(f"/api/v1/vod/play-info?{q}")

    def vod_playlist_url(self, movie_id: int) -> str:
        # VOD segments are IP-bound on Antik storage; must be proxied (no direct=1).
        q = urllib.parse.urlencode({"movie_id": movie_id})
        return f"{self._base}/stream/vod/playlist.m3u8?{q}"
