"""Token resolution and config persistence."""

from __future__ import annotations

import os
import sys
import time
import webbrowser
from collections.abc import Callable
from pathlib import Path
from typing import Any

import httpx
import tomli_w

CONFIG_PATH = Path.home() / ".config" / "appliku" / "config.toml"
API_BASE_URL = "https://api.appliku.com"


def _load_toml(path: Path) -> dict[str, Any]:
    """Load a TOML file, compatible with Python 3.10+."""
    if sys.version_info >= (3, 11):
        import tomllib

        with open(path, "rb") as f:
            return tomllib.load(f)
    else:
        import tomli  # type: ignore[import-not-found]

        with open(path, "rb") as f:
            return tomli.load(f)  # type: ignore[no-any-return]


def resolve_token(explicit_token: str | None = None) -> str | None:
    """Priority: explicit param > APPLIKU_TOKEN env > config file."""
    if explicit_token:
        return explicit_token
    env = os.environ.get("APPLIKU_TOKEN")
    if env:
        return env
    return _read_config_token()


def _read_config_token() -> str | None:
    """Read token from ~/.config/appliku/config.toml."""
    if not CONFIG_PATH.exists():
        return None
    try:
        data = _load_toml(CONFIG_PATH)
        auth_section = data.get("auth", {})
        token = auth_section.get("token") if isinstance(auth_section, dict) else None
        return str(token) if token is not None else None
    except Exception:
        return None


def save_token(token: str) -> None:
    """Persist token to config file."""
    CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
    data: dict[str, Any] = {"auth": {"token": token}}
    with open(CONFIG_PATH, "wb") as f:
        tomli_w.dump(data, f)


class DeviceAuthFlow:
    """Handles the browser-based device authorization flow."""

    def __init__(self, base_url: str | None = None) -> None:
        self.base_url = base_url or API_BASE_URL
        self.device_code: str | None = None
        self.user_code: str | None = None
        self.verification_url: str | None = None
        self.verification_url_complete: str | None = None
        self.expires_in: int = 0
        self.interval: int = 5
        self._client = httpx.Client(base_url=self.base_url, timeout=30.0)

    def initiate(self) -> None:
        """Start the device authorization flow."""
        response = self._client.post(
            "/api/cli/auth/initiate/",
            json={"client_name": "Appliku CLI"},
        )
        response.raise_for_status()
        data = response.json()

        self.device_code = data["device_code"]
        self.user_code = data["user_code"]
        self.verification_url = data["verification_url"]
        self.verification_url_complete = data["verification_url_complete"]
        self.expires_in = data["expires_in"]
        self.interval = data.get("interval", 5)

    def open_browser(self) -> bool:
        """Attempt to open the browser. Returns True if successful."""
        if self.verification_url_complete:
            try:
                webbrowser.open(self.verification_url_complete)
                return True
            except Exception:
                return False
        return False

    def poll(self) -> tuple[str, str | None]:
        """
        Poll for authorization status.
        Returns (status, api_token).
        Status: 'pending', 'authorized', 'denied', 'expired'
        """
        if not self.device_code:
            raise RuntimeError("Must call initiate() first")

        response = self._client.post(
            "/api/cli/auth/poll/",
            json={"device_code": self.device_code},
        )

        if response.status_code == 202:
            return ("pending", None)
        elif response.status_code == 200:
            data = response.json()
            return (data["status"], data.get("api_token"))
        elif response.status_code == 410:
            return ("expired", None)
        elif response.status_code == 403:
            return ("denied", None)
        elif response.status_code == 404:
            return ("expired", None)
        else:
            response.raise_for_status()
            return ("error", None)

    def wait_for_authorization(
        self,
        on_pending: Callable[[int], None] | None = None,
        timeout: int | None = None,
    ) -> str | None:
        """
        Poll until authorized, denied, or expired.
        Returns the API token if authorized, None otherwise.
        on_pending is called with seconds_elapsed on each poll.
        """
        start_time = time.time()
        effective_timeout = timeout or self.expires_in

        while True:
            elapsed = int(time.time() - start_time)

            if elapsed > effective_timeout:
                return None

            status, token = self.poll()

            if status == "authorized":
                return token
            elif status in ("denied", "expired"):
                return None
            elif status == "pending":
                if on_pending:
                    on_pending(elapsed)
                time.sleep(self.interval)
            else:
                return None

    def close(self) -> None:
        """Close the HTTP client."""
        self._client.close()
