"""Read session cookie from local Cursor installation."""
from __future__ import annotations

import base64
import json
import os
import sqlite3
import sys
import threading
import time
from dataclasses import dataclass
from pathlib import Path

from errors import InvalidSessionError, NotLoggedInError, StateDbNotFoundError


def _default_state_db() -> Path:
    if sys.platform == "win32":
        appdata = os.environ.get("APPDATA")
        if not appdata:
            raise RuntimeError("APPDATA is not set")
        return Path(appdata) / "Cursor" / "User" / "globalStorage" / "state.vscdb"

    config_home = os.environ.get("XDG_CONFIG_HOME")
    base = Path(config_home) if config_home else Path.home() / ".config"
    return base / "Cursor" / "User" / "globalStorage" / "state.vscdb"


STATE_DB = _default_state_db()

_PLAN_LABELS = {
    "free": "Free",
    "pro": "PRO",
    "pro_plus": "PRO+",
    "business": "Business",
    "ultra": "Ultra",
}

_READ_RETRIES = 3
_READ_RETRY_DELAY = 0.15
_EXPIRY_SKEW_SEC = 60
_FALLBACK_TTL_SEC = 300


@dataclass
class _SessionCache:
    cookie: str
    expires_at: float


_cache: _SessionCache | None = None
_cache_lock = threading.Lock()


def _db_connect() -> sqlite3.Connection:
    return sqlite3.connect(f"file:{STATE_DB}?mode=ro", uri=True, timeout=5)


def _read_db_value(key: str) -> str | None:
    if not STATE_DB.exists():
        return None

    last_error: sqlite3.Error | None = None
    for attempt in range(_READ_RETRIES):
        try:
            with _db_connect() as conn:
                row = conn.execute(
                    "SELECT value FROM ItemTable WHERE key=?",
                    (key,),
                ).fetchone()
            if not row:
                return None
            value = row[0]
            if isinstance(value, bytes):
                value = value.decode("utf-8", errors="replace")
            return str(value).strip('"')
        except sqlite3.Error as exc:
            last_error = exc
            if attempt + 1 < _READ_RETRIES:
                time.sleep(_READ_RETRY_DELAY)

    if last_error is not None:
        raise last_error
    return None


def get_membership_type() -> str | None:
    try:
        return _read_db_value("cursorAuth/stripeMembershipType")
    except sqlite3.Error:
        return None


def format_plan_label(membership_type: str | None) -> str | None:
    if not membership_type:
        return None
    if membership_type in _PLAN_LABELS:
        return _PLAN_LABELS[membership_type]
    return membership_type.replace("_", " ").upper()


def get_plan_label() -> str | None:
    return format_plan_label(get_membership_type())


def get_user_label() -> str | None:
    try:
        profile_raw = _read_db_value("cursorAuth/cachedScopedProfile")
        if profile_raw:
            profile = json.loads(profile_raw)
            display_name = profile.get("displayName")
            if display_name and str(display_name).strip():
                return str(display_name).strip()
        email = _read_db_value("cursorAuth/cachedEmail")
        if email and email.strip():
            return email.strip()
    except (sqlite3.Error, json.JSONDecodeError, KeyError, TypeError):
        return None
    return None


def invalidate_session_cache() -> None:
    global _cache
    with _cache_lock:
        _cache = None


def _cache_valid(cache: _SessionCache) -> bool:
    return time.time() < cache.expires_at - _EXPIRY_SKEW_SEC


def _decode_jwt_payload(token: str) -> dict:
    parts = token.split(".")
    if len(parts) != 3:
        raise InvalidSessionError("invalid token format")
    payload_b64 = parts[1]
    payload_b64 += "=" * (-len(payload_b64) % 4)
    try:
        payload = json.loads(base64.urlsafe_b64decode(payload_b64))
    except (json.JSONDecodeError, ValueError, TypeError) as exc:
        raise InvalidSessionError("invalid token payload") from exc
    if not isinstance(payload, dict):
        raise InvalidSessionError("invalid token payload")
    return payload


def _session_expires_at(payload: dict) -> float:
    exp = payload.get("exp")
    if isinstance(exp, (int, float)) and exp > 0:
        return float(exp)
    return time.time() + _FALLBACK_TTL_SEC


def _build_session_cookie(token: str) -> tuple[str, float]:
    payload = _decode_jwt_payload(token)
    sub = payload.get("sub")
    if not sub or not isinstance(sub, str):
        raise InvalidSessionError("missing sub claim")
    user_id = sub.split("|")[-1]
    cookie = f"WorkosCursorSessionToken={user_id}::{token}"
    return cookie, _session_expires_at(payload)


def _load_session_cookie() -> _SessionCache:
    if not STATE_DB.exists():
        raise StateDbNotFoundError()

    token = _read_db_value("cursorAuth/accessToken")
    if not token:
        raise NotLoggedInError()

    cookie, expires_at = _build_session_cookie(token)
    return _SessionCache(cookie=cookie, expires_at=expires_at)


def get_session_cookie() -> str:
    global _cache
    with _cache_lock:
        if _cache is not None and _cache_valid(_cache):
            return _cache.cookie
        _cache = _load_session_cookie()
        return _cache.cookie
