"""Safe user-facing error messages and diagnostic logging."""
from __future__ import annotations

import os
import re
import sqlite3
import sys
import urllib.error
from datetime import datetime, timezone
from pathlib import Path


def _default_log_dir() -> Path:
    if sys.platform == "win32":
        base = os.environ.get("LOCALAPPDATA") or os.environ.get("APPDATA")
        if not base:
            raise RuntimeError("LOCALAPPDATA/APPDATA is not set")
        return Path(base) / "Cursor Metrics"

    xdg_state = os.environ.get("XDG_STATE_HOME")
    base = Path(xdg_state) if xdg_state else Path.home() / ".local" / "state"
    return base / "cursor-metrics"


_LOG_DIR = _default_log_dir()
_LOG_PATH = _LOG_DIR / "app.log"
_REDACT_PATTERNS = (
    re.compile(r"WorkosCursorSessionToken=[^\s;]+", re.I),
    re.compile(r"Bearer\s+\S+", re.I),
    re.compile(r"Cookie:\s*[^\n]+", re.I),
    re.compile(r"eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+"),
)

_MSG_FAILED = "Failed to load usage data."
_MSG_NETWORK = "Network error — check your connection."
_MSG_DB_BUSY = "Cursor database busy — try again in a moment."
_MSG_DB_NOT_FOUND = "Cursor database not found — install and run Cursor once."
_MSG_NOT_LOGGED_IN = "Not logged in to Cursor — sign in and restart the app."
_MSG_SESSION_INVALID = "Session invalid — sign in to Cursor again."


class ApiHttpError(RuntimeError):
    def __init__(self, status_code: int, detail: str = "") -> None:
        self.status_code = status_code
        self.detail = detail
        super().__init__(f"HTTP {status_code}")


class StateDbNotFoundError(Exception):
    """Cursor state.vscdb is missing."""


class NotLoggedInError(Exception):
    """cursorAuth/accessToken missing."""


class InvalidSessionError(Exception):
    """JWT from Cursor DB is missing or malformed."""


def _redact(text: str) -> str:
    redacted = text
    for pattern in _REDACT_PATTERNS:
        redacted = pattern.sub("[REDACTED]", redacted)
    return redacted


def _http_message(status_code: int) -> str:
    if status_code == 401:
        return "Session expired — log in to Cursor again."
    if status_code == 403:
        return "Access denied — check your Cursor login."
    if status_code >= 500:
        return "Cursor API unavailable — try again later."
    return _MSG_FAILED


def user_facing_error(exc: BaseException) -> str:
    if isinstance(exc, ApiHttpError):
        return _http_message(exc.status_code)
    if isinstance(exc, StateDbNotFoundError):
        return _MSG_DB_NOT_FOUND
    if isinstance(exc, NotLoggedInError):
        return _MSG_NOT_LOGGED_IN
    if isinstance(exc, InvalidSessionError):
        return _MSG_SESSION_INVALID
    if isinstance(exc, FileNotFoundError):
        return _MSG_DB_NOT_FOUND
    if isinstance(exc, sqlite3.OperationalError):
        if "locked" in str(exc).lower():
            return _MSG_DB_BUSY
        return _MSG_DB_BUSY
    if isinstance(exc, sqlite3.Error):
        return _MSG_DB_BUSY
    if isinstance(exc, TimeoutError):
        return _MSG_NETWORK
    if isinstance(exc, urllib.error.URLError):
        return _MSG_NETWORK
    if isinstance(exc, RuntimeError):
        text = str(exc)
        if "accessToken missing" in text or "Not logged in" in text:
            return _MSG_NOT_LOGGED_IN
        if "access token" in text.lower() or "access tokenu" in text.lower():
            return _MSG_SESSION_INVALID
        if text.startswith("API "):
            return _MSG_FAILED
    return _MSG_FAILED


def _diagnostic_detail(exc: BaseException) -> str:
    if isinstance(exc, ApiHttpError):
        parts = [f"HTTP {exc.status_code}"]
        if exc.detail:
            parts.append(exc.detail)
        return " | ".join(parts)
    return _redact(str(exc))


def log_error(exc: BaseException) -> None:
    try:
        _LOG_DIR.mkdir(parents=True, exist_ok=True)
        timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
        line = f"{timestamp} {type(exc).__name__}: {_diagnostic_detail(exc)}\n"
        with _LOG_PATH.open("a", encoding="utf-8") as handle:
            handle.write(line)
    except OSError:
        pass
