"""
Crash/error telemetry — capture, store, and report errors for production monitoring.

Non-blocking: errors are written to disk and never thrown back to the caller.
No network calls: all data stays local. Opt-in remote reporting via webhook.

Usage:
  from src.telemetry.tracker import capture_exception, get_error_report

  try:
      ...
  except Exception:
      capture_exception()  # logs to file, doesn't re-raise

  # Or as a context manager:
  with error_context("pipeline.execute"):
      ...
"""
from __future__ import annotations

import json
import os
import sys
import threading
import traceback
from contextlib import contextmanager
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

TELEMETRY_DIR = Path(__file__).resolve().parent.parent.parent / "data" / "telemetry"
ERRORS_FILE = TELEMETRY_DIR / "errors.ndjson"
CRASHES_FILE = TELEMETRY_DIR / "crashes.ndjson"
METRICS_FILE = TELEMETRY_DIR / "metrics.ndjson"

MAX_ERRORS = int(os.environ.get("GAT_TELEMETRY_MAX_ERRORS", "10000"))
MAX_CRASHES = int(os.environ.get("GAT_TELEMETRY_MAX_CRASHES", "1000"))
MAX_METRICS = int(os.environ.get("GAT_TELEMETRY_MAX_METRICS", "50000"))

_write_lock = threading.Lock()


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _append_jsonl(path: Path, entry: dict[str, Any], max_lines: int) -> None:
    from src.data_layer import append_ndjson, read_ndjson
    with _write_lock:
        append_ndjson(path, entry)
        # 行数限制：定期裁剪
        lines = read_ndjson(path)
        if len(lines) > max_lines:
            lines = lines[-max_lines:]
            path.write_text("\n".join(json.dumps(l, ensure_ascii=False) for l in lines) + "\n", encoding="utf-8")


# ─── Error Capture ─────────────────────────────────────────────────

def capture_exception(
    error: Exception | None = None,
    context: str = "",
    metadata: dict[str, Any] | None = None,
) -> dict[str, Any]:
    """
    Capture an exception for telemetry. Does NOT re-raise.

    Call this in except blocks to record the error without disrupting execution.
    If error is None, uses sys.exc_info() to get the current exception.
    """
    if error is None:
        exc_type, exc_value, exc_tb = sys.exc_info()
        if exc_value is None:
            return {"ok": False, "reason": "no active exception"}
        error = exc_value
    else:
        exc_tb = error.__traceback__

    tb_lines = traceback.format_exception(type(error), error, exc_tb) if exc_tb else [str(error)]

    entry = {
        "timestamp": _now(),
        "type": type(error).__name__,
        "message": str(error)[:500],
        "context": context,
        "traceback": "".join(tb_lines)[-2000:],  # last 2000 chars
        "metadata": metadata or {},
    }

    _append_jsonl(ERRORS_FILE, entry, MAX_ERRORS)

    # Also write to stderr for immediate visibility
    print(f"[TELEMETRY] {entry['type']}: {entry['message'][:100]}", file=sys.stderr)

    return {"ok": True, "recorded": entry["type"]}


def capture_crash(context: str = "", metadata: dict[str, Any] | None = None) -> dict[str, Any]:
    """
    Record a crash (process-level failure). More detailed than capture_exception.
    """
    exc_type, exc_value, exc_tb = sys.exc_info()
    tb_lines = traceback.format_exception(exc_type, exc_value, exc_tb) if exc_tb else ["unknown"]

    entry = {
        "timestamp": _now(),
        "type": str(exc_type.__name__) if exc_type else "Unknown",
        "message": str(exc_value)[:500] if exc_value else "No error details",
        "context": context,
        "traceback": "".join(tb_lines),
        "metadata": metadata or {},
        "python_version": sys.version,
        "platform": sys.platform,
    }

    _append_jsonl(CRASHES_FILE, entry, MAX_CRASHES)
    return {"ok": True, "recorded": entry["type"]}


# ─── Context Manager ───────────────────────────────────────────────

@contextmanager
def error_context(context: str, capture_as_crash: bool = False):
    """
    Context manager that captures any exception and continues.

    with error_context("pipeline.execute"):
        risky_operation()
    """
    try:
        yield
    except Exception as e:
        if capture_as_crash:
            capture_crash(context)
        else:
            capture_exception(e, context)


# ─── Metrics Tracking ──────────────────────────────────────────────

def track_metric(name: str, value: float, tags: dict[str, str] | None = None) -> None:
    """Record a numeric metric for trending."""
    entry = {
        "timestamp": _now(),
        "metric": name,
        "value": value,
        "tags": tags or {},
    }
    _append_jsonl(METRICS_FILE, entry, MAX_METRICS)


# ─── Reporting ─────────────────────────────────────────────────────

def get_error_report(limit: int = 50, error_type: str = "") -> list[dict[str, Any]]:
    """Get recent errors for dashboard display."""
    if not ERRORS_FILE.exists():
        return []
    try:
        errors = []
        for line in ERRORS_FILE.read_text(encoding="utf-8").splitlines():
            if not line.strip():
                continue
            try:
                e = json.loads(line)
                if error_type and e.get("type", "") != error_type:
                    continue
                errors.append(e)
            except json.JSONDecodeError:
                continue
        return errors[-limit:]
    except Exception:
        return []


def get_error_stats() -> dict[str, Any]:
    """Get error statistics: counts by type, recent frequency."""
    errors = get_error_report(limit=1000)
    if not errors:
        return {"total": 0, "by_type": {}, "recent_24h": 0}

    by_type: dict[str, int] = {}
    recent = 0
    cutoff = datetime.now(timezone.utc).isoformat()[:10]  # today

    for e in errors:
        t = e.get("type", "Unknown")
        by_type[t] = by_type.get(t, 0) + 1
        if e.get("timestamp", "")[:10] == cutoff:
            recent += 1

    return {"total": len(errors), "by_type": by_type, "recent_24h": recent}


# ─── Startup Telemetry ───────────────────────────────────────────────

TELEMETRY_SERVER = "https://license.goldauto.top"
TELEMETRY_ENABLED = os.environ.get("GAT_TELEMETRY_ENABLED", "1") != "0"


def report_startup(
    version: str = "",
    platform: str = "",
    license_status: str = "unknown",
    trial_days_left: int = -1,
) -> bool:
    """
    Non-blocking startup ping — reports version, platform, license status.
    Fails silently if server is unreachable. Opt-out via GAT_TELEMETRY_ENABLED=0.
    """
    if not TELEMETRY_ENABLED:
        return False

    import urllib.request

    payload = json.dumps({
        "version": version,
        "platform": platform or sys.platform,
        "license_status": license_status,
        "trial_days_left": trial_days_left,
    }).encode("utf-8")

    try:
        req = urllib.request.Request(
            f"{TELEMETRY_SERVER}/api/telemetry/startup",
            data=payload,
            headers={"Content-Type": "application/json"},
        )
        with urllib.request.urlopen(req, timeout=5) as resp:
            return resp.status == 200
    except Exception:
        return False
