from __future__ import annotations

import socket
import urllib.error
import urllib.request
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Callable

from niannian.storage.manifest import utc_now_iso

from niannian.storage.feimao_auth import feimao_request_headers

HttpOpener = Callable[..., object]
DEFAULT_FEIMAO_API_BASE = "https://www.feemoo.com"
PROBE_TTL_MINUTES = 15


@dataclass
class FeimaoReachability:
    status: str  # ok | unreachable | auth_failed | unknown
    hint: str | None = None
    error_class: str | None = None

    def to_dict(self) -> dict[str, Any]:
        out: dict[str, Any] = {"status": self.status}
        if self.hint:
            out["hint"] = self.hint
        if self.error_class:
            out["error_class"] = self.error_class
        return out


def probe_is_fresh(last_probe_at: str | None, *, ttl_minutes: int = PROBE_TTL_MINUTES) -> bool:
    if not last_probe_at:
        return False
    try:
        at = datetime.fromisoformat(str(last_probe_at).replace("Z", "+00:00"))
    except ValueError:
        return False
    return at >= datetime.now(timezone.utc) - timedelta(minutes=ttl_minutes)


def feimao_failover_eligible(
    tier_state: dict[str, Any],
    *,
    feimao_cfg: dict[str, Any] | None = None,
    ttl_minutes: int = PROBE_TTL_MINUTES,
) -> bool:
    feimao_cfg = feimao_cfg or {}
    if not feimao_cfg.get("failover_enabled", True):
        return False
    if feimao_cfg.get("skip_failover_when_unreachable", True) is False:
        return True
    ps = (tier_state.get("providers") or {}).get("feimao") or {}
    if ps.get("reachability") != "ok":
        return False
    return probe_is_fresh(ps.get("last_probe_at"), ttl_minutes=ttl_minutes)


class FeimaoReachabilityProbe:
    """区分 unreachable 与 auth_failed — 国内防护。"""

    def __init__(
        self,
        *,
        api_base: str | None = None,
        timeout_seconds: float = 8.0,
        http_opener: HttpOpener | None = None,
        session_token: str | None = None,
    ) -> None:
        self.api_base = (api_base or DEFAULT_FEIMAO_API_BASE).rstrip("/")
        self.timeout_seconds = min(float(timeout_seconds), 8.0)
        self._http_opener = http_opener or urllib.request.urlopen
        self.session_token = session_token

    def probe(self) -> FeimaoReachability:
        url = f"{self.api_base}/v1/ping"
        headers = feimao_request_headers(self.session_token)
        req = urllib.request.Request(url, method="HEAD", headers=headers)
        try:
            with self._http_opener(req, timeout=self.timeout_seconds) as resp:
                status = getattr(resp, "status", 200)
                if status in (401, 403):
                    return FeimaoReachability(
                        status="auth_failed",
                        hint="session_invalid",
                        error_class="auth",
                    )
                if status >= 400:
                    return FeimaoReachability(
                        status="unknown",
                        hint=f"http_{status}",
                        error_class="http",
                    )
                return FeimaoReachability(status="ok")
        except urllib.error.HTTPError as exc:
            if exc.code in (401, 403):
                return FeimaoReachability(status="auth_failed", hint="session_invalid", error_class="auth")
            if exc.code >= 500:
                return FeimaoReachability(status="unreachable", hint="upstream_error", error_class="network")
            return FeimaoReachability(status="unknown", hint=f"http_{exc.code}", error_class="http")
        except (TimeoutError, socket.timeout):
            return FeimaoReachability(
                status="unreachable",
                hint="domestic_network_timeout",
                error_class="timeout",
            )
        except urllib.error.URLError as exc:
            reason = getattr(exc, "reason", exc)
            if isinstance(reason, (TimeoutError, socket.timeout)):
                return FeimaoReachability(
                    status="unreachable",
                    hint="domestic_network_timeout",
                    error_class="timeout",
                )
            return FeimaoReachability(status="unreachable", hint="dns_or_network", error_class="network")
        except OSError:
            return FeimaoReachability(status="unreachable", hint="network_error", error_class="network")


def run_feimao_probe(
    home: Path,
    *,
    config: dict[str, Any] | None = None,
    credential_manager: Any | None = None,
    http_opener: HttpOpener | None = None,
) -> dict[str, Any]:
    from niannian.storage.cloud_tier import read_tier_state, write_tier_state

    config = config or {}
    feimao_cfg = ((config.get("storage") or {}).get("tier") or {}).get("feimao") or {}
    conn = feimao_cfg.get("connectivity") or {}
    timeout = min(float(conn.get("timeout_seconds", 8)), 8.0)
    token = None
    if credential_manager is not None:
        token = credential_manager.get_session_token("feimao")
    probe = FeimaoReachabilityProbe(
        timeout_seconds=timeout,
        http_opener=http_opener,
        session_token=token,
    )
    result = probe.probe()
    now = utc_now_iso()
    state = read_tier_state(home)
    ps = state.setdefault("providers", {}).setdefault("feimao", {})
    ps["reachability"] = result.status
    ps["last_probe_at"] = now
    if result.hint:
        ps["reachability_hint"] = result.hint
    if result.error_class:
        ps["last_error_class"] = result.error_class
    write_tier_state(home, state)
    return {"ok": True, "reachability": result.to_dict(), "at": now}
