"""租户 session token — 内存 + 可选 YAML 持久化。"""

from __future__ import annotations

import secrets
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any

import yaml


def _now_utc() -> datetime:
    return datetime.now(timezone.utc)


def _iso_z(dt: datetime) -> str:
    return dt.replace(microsecond=0).isoformat().replace("+00:00", "Z")


@dataclass(frozen=True)
class TenantSession:
    token: str
    username: str
    expires_at: str
    intranet_console: bool = False

    def to_dict(self) -> dict[str, Any]:
        return {
            "token": self.token,
            "username": self.username,
            "expires_at": self.expires_at,
            "intranet_console": self.intranet_console,
        }


class TenantSessionStore:
    def __init__(self, home: Path, *, ttl_hours: int = 168, persist: bool = True) -> None:
        self._home = Path(home)
        self._ttl = ttl_hours
        self._persist = persist
        self._path = self._home / "control" / "tenant_sessions.yaml"
        self._by_token: dict[str, tuple[str, datetime, bool]] = {}
        if self._persist:
            self._path.parent.mkdir(parents=True, exist_ok=True)
            self._load_persisted()

    @property
    def path(self) -> Path:
        return self._path

    def _load_persisted(self) -> None:
        if not self._path.is_file():
            return
        with self._path.open(encoding="utf-8") as f:
            data = yaml.safe_load(f) or {}
        sessions = data.get("sessions") if isinstance(data, dict) else None
        if not isinstance(sessions, dict):
            return
        now = _now_utc()
        for token, row in sessions.items():
            if not isinstance(row, dict):
                continue
            username = str(row.get("username") or "").strip().lower()
            exp_raw = row.get("expires_at")
            if not username or not exp_raw:
                continue
            try:
                exp = datetime.fromisoformat(str(exp_raw).replace("Z", "+00:00"))
            except ValueError:
                continue
            if exp.tzinfo is None:
                exp = exp.replace(tzinfo=timezone.utc)
            if exp <= now:
                continue
            intranet = bool(row.get("intranet_console", False))
            self._by_token[str(token)] = (username, exp, intranet)

    def _save_persisted(self) -> None:
        if not self._persist:
            return
        sessions: dict[str, dict[str, str]] = {}
        for token, (username, exp, intranet) in self._by_token.items():
            sessions[token] = {
                "username": username,
                "expires_at": _iso_z(exp),
                "intranet_console": intranet,
            }
        payload = {"sessions": sessions}
        with self._path.open("w", encoding="utf-8") as f:
            yaml.safe_dump(payload, f, allow_unicode=True, default_flow_style=False, sort_keys=False)

    def issue(self, username: str, *, intranet_console: bool = False) -> TenantSession:
        name = username.strip().lower()
        token = secrets.token_urlsafe(24)
        expires = _now_utc() + timedelta(hours=self._ttl)
        self._by_token[token] = (name, expires, bool(intranet_console))
        self._save_persisted()
        return TenantSession(
            token=token,
            username=name,
            expires_at=_iso_z(expires),
            intranet_console=bool(intranet_console),
        )

    def validate(self, token: str) -> TenantSession | None:
        if self._persist:
            self._by_token.clear()
            self._load_persisted()
        raw = str(token or "").strip()
        if not raw:
            return None
        row = self._by_token.get(raw)
        if row is None:
            return None
        username, exp, intranet = row
        if _now_utc() > exp:
            self._by_token.pop(raw, None)
            self._save_persisted()
            return None
        return TenantSession(
            token=raw,
            username=username,
            expires_at=_iso_z(exp),
            intranet_console=intranet,
        )

    def revoke_user(self, username: str) -> None:
        if self._persist:
            self._by_token.clear()
            self._load_persisted()
        name = username.strip().lower()
        doomed = [tok for tok, (user, _, __) in self._by_token.items() if user == name]
        for tok in doomed:
            self._by_token.pop(tok, None)
        if doomed:
            self._save_persisted()
