"""Support bundle export for pilot closeout and operator debugging."""

from __future__ import annotations

import hashlib
import json
import re
import tarfile
import tempfile
from datetime import datetime, timedelta, timezone
from importlib import metadata as importlib_metadata
from pathlib import Path
from typing import Any

from .control_plane import ACTION_KEY_REGISTRY, ACTION_KEY_REGISTRY_VERSION
from .control_plane_job_queue import ControlPlaneJobQueue, control_plane_job_queue_db_path
from .event_queue import CrmEventQueue, event_queue_db_path
from .gate_metrics import compute_gate_metrics
from .modules import ModuleStore, infer_module_write_class_from_tool
from .policy import generate_default_policy, load_policy
from .rollback_coverage import rollback_coverage_snapshot

_BUNDLE_VERSION = "v1"
_REDACTED_VALUE = "[REDACTED]"
_SECRET_KEY_RE = re.compile(r"(secret|token|password|key)", re.IGNORECASE)


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


def _utc_now_iso() -> str:
    return _utc_now().isoformat()


def _parse_iso(value: Any) -> datetime | None:
    if not isinstance(value, str) or not value.strip():
        return None
    raw = value.strip()
    if raw.endswith("Z"):
        raw = raw[:-1] + "+00:00"
    try:
        parsed = datetime.fromisoformat(raw)
    except Exception:
        return None
    if parsed.tzinfo is None:
        parsed = parsed.replace(tzinfo=timezone.utc)
    return parsed.astimezone(timezone.utc)


def _model_to_dict(model: Any) -> dict[str, Any]:
    if hasattr(model, "model_dump"):
        payload = model.model_dump(exclude_none=False)
    elif hasattr(model, "to_dict"):
        payload = model.to_dict()
    else:
        payload = {}
    return payload if isinstance(payload, dict) else {}


def _sanitize_payload(value: Any) -> Any:
    if isinstance(value, dict):
        sanitized: dict[str, Any] = {}
        for key, raw_value in value.items():
            normalized_key = str(key)
            if _SECRET_KEY_RE.search(normalized_key):
                sanitized[normalized_key] = _REDACTED_VALUE
                continue
            sanitized[normalized_key] = _sanitize_payload(raw_value)
        return sanitized
    if isinstance(value, list):
        return [_sanitize_payload(item) for item in value]
    return value


def _json_write(path: Path, payload: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(
        json.dumps(payload, indent=2, sort_keys=True, ensure_ascii=True),
        encoding="utf-8",
    )


def _resolve_gremlin_version() -> str:
    try:
        return str(importlib_metadata.version("g-gremlin"))
    except Exception:
        pass
    try:
        return str(importlib_metadata.version("g_gremlin"))
    except Exception:
        return "unknown"


def _state_dir_hash(state_dir: Path) -> str:
    return hashlib.sha256(str(state_dir.resolve()).encode("utf-8")).hexdigest()


def _load_policy_snapshot(state_dir: Path) -> dict[str, Any]:
    runtime_policy_path = state_dir / "policy_runtime.yaml"
    if runtime_policy_path.exists():
        try:
            return _model_to_dict(load_policy(runtime_policy_path))
        except Exception:
            pass
    return _model_to_dict(generate_default_policy("local"))


def _enabled_module_snapshot(state_dir: Path) -> list[dict[str, Any]]:
    store = ModuleStore(state_dir)
    modules: list[dict[str, Any]] = []
    for envelope in store.list_modules():
        payload = _model_to_dict(envelope)
        if bool(payload.get("enabled")):
            modules.append(payload)
    return modules


def _queue_counts(state_dir: Path) -> dict[str, Any]:
    crm_queue = CrmEventQueue(event_queue_db_path(state_dir))
    control_queue = ControlPlaneJobQueue(control_plane_job_queue_db_path(state_dir))
    return {
        "crm_event_queue": crm_queue.queue_counts(),
        "control_plane_job_queue": control_queue.queue_counts(),
    }


def _timeline_timestamp(
    *,
    receipt_payload: dict[str, Any] | None,
    plan_payload: dict[str, Any] | None,
    run_dir: Path,
) -> datetime:
    receipt_candidates = (
        (receipt_payload or {}).get("completed_at"),
        (receipt_payload or {}).get("started_at"),
    )
    for candidate in receipt_candidates:
        parsed = _parse_iso(candidate)
        if parsed is not None:
            return parsed

    plan_candidates = (
        (plan_payload or {}).get("created_at"),
        (plan_payload or {}).get("started_at"),
    )
    for candidate in plan_candidates:
        parsed = _parse_iso(candidate)
        if parsed is not None:
            return parsed
    return datetime.fromtimestamp(run_dir.stat().st_mtime, tz=timezone.utc)


def _extract_module_id(plan_payload: dict[str, Any] | None) -> str | None:
    if not isinstance(plan_payload, dict):
        return None
    steps = plan_payload.get("steps")
    if not isinstance(steps, list):
        return None
    for step in steps:
        if not isinstance(step, dict):
            continue
        module_id, _write_class = infer_module_write_class_from_tool(str(step.get("tool") or ""))
        if module_id:
            return str(module_id)
    return None


def _collect_timeline(
    state_dir: Path,
    *,
    lookback_days: int,
) -> tuple[list[dict[str, Any]], list[tuple[str, dict[str, Any]]]]:
    receipts_dir = state_dir / "receipts"
    if not receipts_dir.exists():
        return [], []

    now = _utc_now()
    window_start = now - timedelta(days=max(1, int(lookback_days)))
    run_summary: list[dict[str, Any]] = []
    receipt_rows: list[tuple[str, dict[str, Any]]] = []

    for run_dir in receipts_dir.iterdir():
        if not run_dir.is_dir():
            continue
        receipt_path = run_dir / "receipt.json"
        plan_path = run_dir / "plan.json"
        if not receipt_path.exists():
            continue

        try:
            receipt_payload = json.loads(receipt_path.read_text(encoding="utf-8"))
        except Exception:
            continue
        if not isinstance(receipt_payload, dict):
            continue

        plan_payload: dict[str, Any] | None = None
        if plan_path.exists():
            try:
                parsed_plan = json.loads(plan_path.read_text(encoding="utf-8"))
            except Exception:
                parsed_plan = None
            if isinstance(parsed_plan, dict):
                plan_payload = parsed_plan

        timestamp = _timeline_timestamp(
            receipt_payload=receipt_payload,
            plan_payload=plan_payload,
            run_dir=run_dir,
        )
        if timestamp < window_start:
            continue
        timestamp_iso = timestamp.isoformat()

        run_id = str(receipt_payload.get("run_id") or run_dir.name)
        risk_block = plan_payload.get("risk") if isinstance(plan_payload, dict) else {}
        risk_tier = ""
        if isinstance(risk_block, dict):
            risk_tier = str(risk_block.get("tier") or "").strip()

        run_summary.append(
            {
                "run_id": run_id,
                "outcome": str(receipt_payload.get("outcome") or ""),
                "timestamp": timestamp_iso,
                "module_id": _extract_module_id(plan_payload),
                "risk_tier": risk_tier or None,
            }
        )
        receipt_rows.append((run_id, receipt_payload))

    run_summary.sort(key=lambda row: str(row.get("timestamp") or ""), reverse=True)
    receipt_rows.sort(
        key=lambda item: str(next((row["timestamp"] for row in run_summary if row["run_id"] == item[0]), "")),
        reverse=True,
    )
    return run_summary, receipt_rows


def build_support_bundle(
    state_dir: Path,
    *,
    output_path: Path | None = None,
    include_receipts: bool = True,
    lookback_days: int = 30,
) -> Path:
    """Build and return a support bundle archive path."""

    resolved_state_dir = Path(state_dir).resolve()
    bounded_lookback = max(1, min(int(lookback_days), 3650))
    created_at = _utc_now_iso()
    if output_path is None:
        timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
        output_path = resolved_state_dir / "exports" / f"support-bundle-{timestamp}.tar.gz"
    resolved_output_path = Path(output_path).resolve()
    resolved_output_path.parent.mkdir(parents=True, exist_ok=True)

    from .readiness_gate import autonomy_operational_health, check_autonomy_readiness

    # Imported lazily to avoid circular import from server -> support_bundle.
    from . import server as server_mod

    manifest = {
        "bundle_version": _BUNDLE_VERSION,
        "created_at": created_at,
        "g_gremlin_version": _resolve_gremlin_version(),
        "state_dir_hash": _state_dir_hash(resolved_state_dir),
        "lookback_days": bounded_lookback,
        "include_receipts": bool(include_receipts),
    }
    policy_snapshot = _sanitize_payload(_load_policy_snapshot(resolved_state_dir))
    modules_snapshot = _sanitize_payload(_enabled_module_snapshot(resolved_state_dir))
    action_registry_payload = {
        "action_registry_version": ACTION_KEY_REGISTRY_VERSION,
        "actions": ACTION_KEY_REGISTRY,
    }
    rollback_coverage = rollback_coverage_snapshot()
    pilot_metrics = server_mod._compute_pilot_metrics(resolved_state_dir)
    gate_metrics = compute_gate_metrics(resolved_state_dir, lookback_days=bounded_lookback)
    queue_counts = _queue_counts(resolved_state_dir)
    readiness = check_autonomy_readiness(resolved_state_dir)
    operational_health = (
        readiness.get("operational_health")
        if isinstance(readiness, dict) and isinstance(readiness.get("operational_health"), dict)
        else autonomy_operational_health(resolved_state_dir, lookback_days=bounded_lookback)
    )
    run_summary, receipts = _collect_timeline(
        resolved_state_dir,
        lookback_days=bounded_lookback,
    )

    with tempfile.TemporaryDirectory(prefix="g-gremlin-support-bundle-") as tmp_dir:
        root = Path(tmp_dir) / "bundle"
        _json_write(root / "manifest.json", manifest)
        _json_write(root / "config" / "policy.json", policy_snapshot)
        _json_write(root / "config" / "modules.json", modules_snapshot)
        _json_write(root / "config" / "action_registry.json", action_registry_payload)
        _json_write(root / "config" / "rollback_coverage.json", rollback_coverage)
        _json_write(root / "metrics" / "pilot_metrics.json", pilot_metrics)
        _json_write(root / "metrics" / "gate_metrics.json", gate_metrics)
        _json_write(root / "metrics" / "queue_counts.json", queue_counts)
        _json_write(root / "metrics" / "autonomy_operational.json", operational_health)
        _json_write(root / "timeline" / "run_summary.json", run_summary)
        _json_write(root / "readiness" / "autonomy_gate.json", readiness)

        if include_receipts:
            for run_id, receipt_payload in receipts:
                _json_write(root / "timeline" / "receipts" / f"{run_id}.json", receipt_payload)

        with tarfile.open(resolved_output_path, mode="w:gz") as archive:
            for path in sorted(root.rglob("*")):
                if not path.is_file():
                    continue
                archive.add(path, arcname=str(path.relative_to(root)))

    return resolved_output_path


__all__ = ["build_support_bundle"]

