"""Paychek account / local session for Safeguard.

Primary UX: activation code only (no email / password on the desktop gate).
Legacy Firebase email/password login remains available via API but is not
required to use the app. Optional local email may still be stored if set.
"""
from __future__ import annotations

import json
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any

import httpx

from config import settings
from runtime.paths import user_data_dir

_ACCOUNT_FILE = "paychek_account.json"
_TRIAL_DAYS = 7
_CACHE_HOURS = 12
_SIGNUP_URL = "https://paychek.pro/licence.html"
_PROJECT_ID = "paychek-trading"


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


def _iso(dt: datetime | None) -> str | None:
    return dt.isoformat() if dt else None


def _parse_iso(raw: str | None) -> datetime | None:
    if not raw:
        return None
    try:
        return datetime.fromisoformat(raw.replace("Z", "+00:00"))
    except ValueError:
        return None


def _account_path() -> Path:
    return user_data_dir() / _ACCOUNT_FILE


def _read_account() -> dict[str, Any]:
    path = _account_path()
    if not path.is_file():
        return {}
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except json.JSONDecodeError:
        return {}
    return data if isinstance(data, dict) else {}


def _write_account(data: dict[str, Any]) -> None:
    path = _account_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")


def _api_key() -> str:
    return (settings.paychek_firebase_api_key or "").strip()


def _firestore_get(path: str, id_token: str) -> dict[str, Any] | None:
    url = (
        f"https://firestore.googleapis.com/v1/projects/{_PROJECT_ID}"
        f"/databases/(default)/documents/{path}"
    )
    try:
        with httpx.Client(timeout=12.0) as client:
            r = client.get(url, headers={"Authorization": f"Bearer {id_token}"})
        if r.status_code == 404:
            return None
        if r.status_code >= 400:
            return None
        return r.json()
    except httpx.HTTPError:
        return None


def _field(doc: dict[str, Any] | None, name: str) -> Any:
    if not doc:
        return None
    fields = doc.get("fields") or {}
    node = fields.get(name)
    if not isinstance(node, dict):
        return None
    if "stringValue" in node:
        return node["stringValue"]
    if "booleanValue" in node:
        return bool(node["booleanValue"])
    if "integerValue" in node:
        return int(node["integerValue"])
    if "doubleValue" in node:
        return float(node["doubleValue"])
    if "timestampValue" in node:
        return _parse_iso(str(node["timestampValue"]))
    return None


def _sign_in_email_password(email: str, password: str) -> dict[str, Any]:
    key = _api_key()
    if not key:
        raise ValueError("Firebase API key missing (PAYCHEK_FIREBASE_API_KEY).")
    url = f"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={key}"
    with httpx.Client(timeout=15.0) as client:
        r = client.post(
            url,
            json={
                "email": email.strip(),
                "password": password,
                "returnSecureToken": True,
            },
        )
    data = r.json() if r.content else {}
    if r.status_code >= 400:
        msg = data.get("error", {}).get("message") or "Login failed"
        if msg == "EMAIL_NOT_FOUND":
            raise ValueError("No Paychek account with this email. Sign up on paychek.pro first.")
        if msg == "INVALID_PASSWORD" or msg == "INVALID_LOGIN_CREDENTIALS":
            raise ValueError("Wrong email or password.")
        if msg == "USER_DISABLED":
            raise ValueError("This account is disabled.")
        raise ValueError(str(msg).replace("_", " ").title())
    return data


def _refresh_id_token(refresh_token: str) -> dict[str, Any] | None:
    key = _api_key()
    if not key or not refresh_token:
        return None
    url = f"https://securetoken.googleapis.com/v1/token?key={key}"
    try:
        with httpx.Client(timeout=12.0) as client:
            r = client.post(
                url,
                data={
                    "grant_type": "refresh_token",
                    "refresh_token": refresh_token,
                },
            )
        if r.status_code >= 400:
            return None
        return r.json()
    except httpx.HTTPError:
        return None


def _fetch_entitlement(uid: str, id_token: str) -> dict[str, Any]:
    user_doc = _firestore_get(f"paychek_users/{uid}", id_token)
    ent_doc = _firestore_get(f"subscriber_entitlements/{uid}", id_token)

    created_at = _field(user_doc, "createdAt")
    if not isinstance(created_at, datetime):
        created_at = None
    override_until = _field(user_doc, "trialFreemiumOverrideUntil")
    if not isinstance(override_until, datetime):
        override_until = None
    is_premium = bool(_field(user_doc, "isPremium"))
    email = _field(user_doc, "email")
    if not isinstance(email, str):
        email = None

    sub_active = bool(_field(ent_doc, "active"))
    period_end = _field(ent_doc, "currentPeriodEnd")
    if isinstance(period_end, datetime) and period_end.tzinfo is None:
        period_end = period_end.replace(tzinfo=timezone.utc)
    if sub_active and isinstance(period_end, datetime) and period_end < _utcnow():
        sub_active = False

    now = _utcnow()
    trial_ends = None
    trial_active = False
    if override_until is not None:
        if override_until.tzinfo is None:
            override_until = override_until.replace(tzinfo=timezone.utc)
        trial_ends = override_until
        trial_active = now < override_until
    elif created_at is not None:
        if created_at.tzinfo is None:
            created_at = created_at.replace(tzinfo=timezone.utc)
        trial_ends = created_at + timedelta(days=_TRIAL_DAYS)
        trial_active = now < trial_ends

    journal_pro = is_premium or sub_active
    days_left = 0
    if trial_ends is not None:
        seconds_left = (trial_ends - now).total_seconds()
        days_left = max(0, int((seconds_left + 86399) // 86400)) if seconds_left > 0 else 0

    return {
        "uid": uid,
        "email": email,
        "journal_pro": journal_pro,
        "trial_active": trial_active and not journal_pro,
        "trial_started_at": _iso(created_at),
        "trial_ends_at": _iso(trial_ends),
        "trial_days_left": days_left if (trial_active and not journal_pro) else (0 if not journal_pro else None),
        "fetched_at": _iso(now),
    }


def login_with_email_password(email: str, password: str) -> dict[str, Any]:
    email = (email or "").strip().lower()
    if not email or "@" not in email:
        raise ValueError("Enter a valid email.")
    if not password:
        raise ValueError("Enter your password.")

    auth = _sign_in_email_password(email, password)
    uid = str(auth.get("localId") or "")
    id_token = str(auth.get("idToken") or "")
    refresh = str(auth.get("refreshToken") or "")
    if not uid or not id_token:
        raise ValueError("Login failed (empty token).")

    entitlement = _fetch_entitlement(uid, id_token)
    session = {
        "uid": uid,
        "email": auth.get("email") or email,
        "id_token": id_token,
        "refresh_token": refresh,
        "expires_at": _iso(_utcnow() + timedelta(seconds=int(auth.get("expiresIn") or 3600))),
        "entitlement": entitlement,
        "logged_in_at": _iso(_utcnow()),
        "local_session": False,
    }
    _write_account(session)
    return account_status(refresh=False)


def set_local_email_session(email: str) -> dict[str, Any]:
    """Store registered email for Safeguard (no Firebase password)."""
    email = (email or "").strip().lower()
    if not email or "@" not in email:
        raise ValueError("Enter a valid email.")
    previous = _read_account()
    # Keep Firebase tokens if already present; always refresh display email.
    session = dict(previous) if previous else {}
    session["email"] = email
    session["local_session"] = True
    session["logged_in_at"] = _iso(_utcnow())
    if not session.get("uid"):
        session.pop("id_token", None)
        session.pop("refresh_token", None)
        session.pop("expires_at", None)
        session.pop("entitlement", None)
    _write_account(session)
    return account_status(refresh=False)


def logout_account() -> dict[str, Any]:
    _write_account({})
    return account_status(refresh=False)


def _ensure_fresh_token(session: dict[str, Any]) -> dict[str, Any]:
    expires = _parse_iso(session.get("expires_at"))
    if expires and expires > _utcnow() + timedelta(minutes=2):
        return session
    refreshed = _refresh_id_token(str(session.get("refresh_token") or ""))
    if not refreshed:
        return session
    session = dict(session)
    session["id_token"] = refreshed.get("id_token") or session.get("id_token")
    session["refresh_token"] = refreshed.get("refresh_token") or session.get("refresh_token")
    expires_in = int(refreshed.get("expires_in") or 3600)
    session["expires_at"] = _iso(_utcnow() + timedelta(seconds=expires_in))
    _write_account(session)
    return session


def get_id_token(*, refresh: bool = True) -> str | None:
    """Return a Firebase ID token for callable Cloud Functions, if logged in."""
    session = _read_account()
    if not session.get("uid"):
        return None
    if refresh:
        session = _ensure_fresh_token(session)
    token = str(session.get("id_token") or "").strip()
    return token or None


def account_status(*, refresh: bool = True) -> dict[str, Any]:
    session = _read_account()
    uid = session.get("uid")
    email = session.get("email")
    local_only = bool(session.get("local_session")) and not uid

    if not uid and not (local_only and email):
        return {
            "logged_in": False,
            "local_session": False,
            "signup_url": _SIGNUP_URL,
            "email": None,
            "uid": None,
            "journal_pro": False,
            "trial_active": False,
            "trial_days_left": 0,
            "trial_started_at": None,
            "trial_ends_at": None,
            "reason": "license_required",
        }

    if local_only:
        return {
            "logged_in": True,
            "local_session": True,
            "signup_url": _SIGNUP_URL,
            "email": email,
            "uid": None,
            "journal_pro": False,
            "trial_active": False,
            "trial_days_left": 0,
            "trial_started_at": None,
            "trial_ends_at": None,
            "reason": "ok",
        }

    entitlement = session.get("entitlement") if isinstance(session.get("entitlement"), dict) else {}
    fetched_at = _parse_iso(entitlement.get("fetched_at"))
    stale = True
    if fetched_at and fetched_at > _utcnow() - timedelta(hours=_CACHE_HOURS):
        stale = False

    if refresh and (stale or not entitlement):
        session = _ensure_fresh_token(session)
        id_token = str(session.get("id_token") or "")
        if id_token:
            try:
                entitlement = _fetch_entitlement(str(uid), id_token)
                session["entitlement"] = entitlement
                if entitlement.get("email"):
                    session["email"] = entitlement["email"]
                _write_account(session)
            except Exception:
                pass

    return {
        "logged_in": True,
        "local_session": bool(session.get("local_session")),
        "signup_url": _SIGNUP_URL,
        "email": session.get("email"),
        "uid": uid,
        "journal_pro": bool(entitlement.get("journal_pro")),
        "trial_active": bool(entitlement.get("trial_active")),
        "trial_days_left": entitlement.get("trial_days_left") or 0,
        "trial_started_at": entitlement.get("trial_started_at"),
        "trial_ends_at": entitlement.get("trial_ends_at"),
        "reason": "ok",
    }
