"""
Agent market governance snapshot
================================

Builds a single read-only summary from the market watch governance reports. The
snapshot is a dashboard artifact only; it does not approve priority upgrades,
scorecard updates, replay, SDK installation, paid API calls, shadow/canary, or
production routing changes.
"""

from __future__ import annotations

import json
from datetime import datetime, time, timedelta, timezone
from pathlib import Path
from typing import Any
from zoneinfo import ZoneInfo

from src.services.snapshot_paths import default_evaluations_dir

_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__))
_SNAPSHOT_PATTERN = "agent_market_governance_snapshot_*.json"
_MARKET_WATCH_WORKFLOW = ".gitea/workflows/agent-market-watch.yaml"
_TAIPEI_TZ = ZoneInfo("Asia/Taipei")
_FRESHNESS_SLA_HOURS = 168
_STALE_GRACE_HOURS = 6


def build_agent_market_governance_snapshot(
    *,
    watch_report: dict[str, Any],
    integration_review: dict[str, Any],
    discovery_classification: dict[str, Any],
    promotion_review: dict[str, Any],
    candidate_registry: dict[str, Any],
    generated_at: str | None = None,
) -> dict[str, Any]:
    """Build the operator-facing market governance snapshot."""
    _require_schema(watch_report, "agent_market_watch_report_v1", "watch_report")
    _require_schema(integration_review, "agent_market_integration_review_v1", "integration_review")
    _require_schema(
        discovery_classification,
        "agent_market_discovery_classification_v1",
        "discovery_classification",
    )
    _require_schema(
        promotion_review,
        "agent_market_watch_promotion_review_v1",
        "promotion_review",
    )

    approvals = _approval_summary(integration_review, discovery_classification, promotion_review)
    candidate_groups = _candidate_groups(
        candidate_registry=candidate_registry,
        integration_review=integration_review,
        promotion_review=promotion_review,
    )
    current_decision = (
        "openclaw_remains_production_decision_core"
        if approvals["replacement_decisions_approved"] == 0
        else "manual_review_required_unexpected_replacement_approval"
    )
    snapshot_generated_at = generated_at or datetime.now(timezone.utc).isoformat()  # noqa: UP017
    cadence = _evaluation_cadence(snapshot_generated_at)
    candidate_statuses = _candidate_statuses(
        watch_report=watch_report,
        candidate_registry=candidate_registry,
        integration_review=integration_review,
        promotion_review=promotion_review,
    )
    summary = {
        "candidate_count": int((watch_report.get("summary") or {}).get("candidate_count", 0)),
        "source_count": int((watch_report.get("summary") or {}).get("source_count", 0)),
        "source_failures": int((watch_report.get("summary") or {}).get("failure_count", 0)),
        "changed_candidates": int(
            (watch_report.get("summary") or {}).get("changed_candidates", 0)
        ),
        "integration_queue_count": int(
            (watch_report.get("summary") or {}).get("integration_queue_count", 0)
        ),
        "blocked_from_integration": int(
            (integration_review.get("summary") or {}).get("blocked_from_integration", 0)
        ),
        "watch_only_candidates_reviewed": int(
            (promotion_review.get("summary") or {}).get(
                "watch_only_candidates_reviewed", 0
            )
        ),
        "eligible_for_market_scorecard_prescreen": int(
            (promotion_review.get("summary") or {}).get(
                "eligible_for_market_scorecard_prescreen", 0
            )
        ),
        "recommended_watch_additions_remaining": int(
            (discovery_classification.get("summary") or {}).get(
                "recommended_watch_additions", 0
            )
        ),
        **approvals,
    }
    return {
        "schema_version": "agent_market_governance_snapshot_v1",
        "generated_at": snapshot_generated_at,
        "inputs": {
            "watch_report_generated_at": watch_report.get("generated_at"),
            "integration_review_generated_at": integration_review.get("generated_at"),
            "discovery_classification_generated_at": discovery_classification.get("generated_at"),
            "promotion_review_generated_at": promotion_review.get("generated_at"),
            "candidate_registry_schema_version": str(candidate_registry.get("schema_version", "")),
        },
        "policy": {
            "snapshot_is_decision_source": False,
            "priority_upgrade_approved": False,
            "market_scorecard_update_approved": False,
            "replay_candidate_approved": False,
            "sdk_installation_approved": False,
            "paid_api_calls_approved": False,
            "production_changes_approved": False,
            "shadow_or_canary_approved": False,
            "replacement_decision_allowed": False,
        },
        "evaluation_cadence": cadence,
        "market_watch_health": _market_watch_health(
            summary=summary,
            cadence=cadence,
        ),
        "current_decision": current_decision,
        "summary": summary,
        "candidate_groups": candidate_groups,
        "candidate_statuses": candidate_statuses,
        "operator_decision_queue": _operator_decision_queue(
            candidate_statuses=candidate_statuses,
            integration_review=integration_review,
            promotion_review=promotion_review,
        ),
        "next_allowed_actions": _next_allowed_actions(candidate_groups),
        "forbidden_actions_without_new_approval": [
            "replace_openclaw",
            "enter_shadow_or_canary",
            "install_new_agent_sdk",
            "call_paid_provider_api",
            "run_replay_for_watch_only_candidate",
            "change_production_routing",
        ],
    }


def load_latest_agent_market_governance_snapshot(
    evaluations_dir: Path | None = None,
) -> dict[str, Any]:
    """Load the newest committed Agent market governance snapshot."""
    directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR
    candidates = sorted(directory.glob(_SNAPSHOT_PATTERN))
    if not candidates:
        raise FileNotFoundError(f"no governance snapshots found in {directory}")

    latest = candidates[-1]
    with latest.open(encoding="utf-8") as handle:
        payload = json.load(handle)

    if not isinstance(payload, dict):
        raise ValueError(f"{latest}: expected JSON object")
    _require_schema(payload, "agent_market_governance_snapshot_v1", str(latest))
    return payload


def _candidate_groups(
    *,
    candidate_registry: dict[str, Any],
    integration_review: dict[str, Any],
    promotion_review: dict[str, Any],
) -> dict[str, list[str]]:
    integration_by_id = {
        str(review.get("candidate_id")): review for review in integration_review.get("reviews") or []
    }
    promotion_ready = [
        str(review.get("candidate_id"))
        for review in promotion_review.get("reviews") or []
        if review.get("eligible_for_market_scorecard_prescreen")
    ]
    baseline = []
    replay_blocked = []
    watch_only = []
    for candidate in candidate_registry.get("candidates") or []:
        candidate_id = str(candidate.get("candidate_id", ""))
        if candidate_id == "openclaw_incumbent":
            baseline.append(candidate_id)
            continue
        if _is_watch_only(candidate):
            watch_only.append(candidate_id)
            continue
        integration = integration_by_id.get(candidate_id, {})
        decision = str(integration.get("decision") or candidate.get("current_decision") or "")
        if "blocked" in decision or "do_not_integrate" in decision:
            replay_blocked.append(candidate_id)
    return {
        "production_baseline": baseline,
        "replay_or_integration_blocked": sorted(replay_blocked),
        "watch_only_candidates": sorted(watch_only),
        "watch_only_scorecard_prescreen_ready": sorted(promotion_ready),
    }


def _candidate_statuses(
    *,
    watch_report: dict[str, Any],
    candidate_registry: dict[str, Any],
    integration_review: dict[str, Any],
    promotion_review: dict[str, Any],
) -> list[dict[str, Any]]:
    integration_by_id = {
        str(review.get("candidate_id")): review for review in integration_review.get("reviews") or []
    }
    promotion_by_id = {
        str(review.get("candidate_id")): review for review in promotion_review.get("reviews") or []
    }
    watched_candidate_ids = {
        str(candidate.get("candidate_id"))
        for candidate in watch_report.get("candidates") or []
        if candidate.get("candidate_id")
    }
    allowed_candidate_ids = watched_candidate_ids | {"openclaw_incumbent"} if watched_candidate_ids else None
    statuses = []
    for candidate in candidate_registry.get("candidates") or []:
        candidate_id = str(candidate.get("candidate_id", ""))
        if allowed_candidate_ids is not None and candidate_id not in allowed_candidate_ids:
            continue
        integration = integration_by_id.get(candidate_id, {})
        promotion = promotion_by_id.get(candidate_id, {})
        readiness = integration.get("readiness") or {}
        registry_status = integration.get("registry_status") or {}
        approval_boundary = integration.get("approval_boundary") or {}

        is_baseline = candidate_id == "openclaw_incumbent"
        is_watch_only = _is_watch_only(candidate)
        statuses.append({
            "candidate_id": candidate_id,
            "display_name": str(
                integration.get("display_name")
                or promotion.get("display_name")
                or candidate.get("display_name")
                or candidate_id
            ),
            "role": str(
                registry_status.get("role")
                or promotion.get("role")
                or candidate.get("role")
                or ""
            ),
            "evaluation_priority": str(candidate.get("evaluation_priority", "")),
            "gate_status": _candidate_gate_status(
                candidate_id=candidate_id,
                is_watch_only=is_watch_only,
                integration=integration,
                promotion=promotion,
            ),
            "current_gate": _candidate_current_gate(
                is_baseline=is_baseline,
                candidate=candidate,
                integration=integration,
                promotion=promotion,
                readiness=readiness,
            ),
            "required_next_gate": _candidate_required_next_gate(
                is_baseline=is_baseline,
                integration=integration,
                promotion=promotion,
                readiness=readiness,
            ),
            "integration_decision": str(
                integration.get("decision")
                or promotion.get("decision")
                or candidate.get("current_decision")
                or ""
            ),
            "score": _market_score(integration),
            "evidence": {
                "latest_replay_summary": registry_status.get("latest_replay_summary")
                or candidate.get("latest_replay_summary"),
                "latest_smoke_gate": registry_status.get("latest_smoke_gate")
                or candidate.get("latest_smoke_gate"),
                "latest_smoke_matrix": registry_status.get("latest_smoke_matrix")
                or candidate.get("latest_smoke_matrix"),
                "latest_smoke_model": registry_status.get("latest_smoke_model")
                or candidate.get("latest_smoke_model"),
            },
            "approvals": {
                "replay": bool(promotion.get("approved_for_replay", False)),
                "sdk_install": bool(
                    approval_boundary.get("approved_for_sdk_install")
                    or promotion.get("approved_for_sdk_install", False)
                ),
                "paid_api": bool(
                    approval_boundary.get("approved_for_paid_api_calls")
                    or promotion.get("approved_for_paid_api_calls", False)
                ),
                "shadow_or_canary": bool(
                    approval_boundary.get("approved_for_shadow_or_canary")
                    or promotion.get("approved_for_shadow_or_canary", False)
                ),
                "production_routing": False,
            },
            "operator_blockers": _candidate_operator_blockers(
                integration=integration,
                promotion=promotion,
            ),
        })
    return statuses


def _operator_decision_queue(
    *,
    candidate_statuses: list[dict[str, Any]],
    integration_review: dict[str, Any],
    promotion_review: dict[str, Any],
) -> list[dict[str, Any]]:
    integration_by_id = {
        str(review.get("candidate_id")): review for review in integration_review.get("reviews") or []
    }
    promotion_by_id = {
        str(review.get("candidate_id")): review for review in promotion_review.get("reviews") or []
    }
    queue = []
    for status in candidate_statuses:
        candidate_id = str(status.get("candidate_id", ""))
        integration = integration_by_id.get(candidate_id, {})
        promotion = promotion_by_id.get(candidate_id, {})
        gate_status = str(status.get("gate_status", ""))
        evidence = status.get("evidence") or {}
        queue.append({
            "candidate_id": candidate_id,
            "display_name": str(status.get("display_name") or candidate_id),
            "priority": _decision_queue_priority(gate_status),
            "queue_status": _decision_queue_status(gate_status),
            "recommended_action": _decision_queue_action(
                candidate_id=candidate_id,
                gate_status=gate_status,
                required_next_gate=str(status.get("required_next_gate") or ""),
            ),
            "approval_boundary": _decision_approval_boundary(
                candidate_id=candidate_id,
                gate_status=gate_status,
                integration=integration,
                promotion=promotion,
            ),
            "risk_notes": _decision_risk_notes(
                candidate_id=candidate_id,
                integration=integration,
                promotion=promotion,
                operator_blockers=status.get("operator_blockers") or [],
            ),
            "evidence_refs": [
                str(value)
                for value in [
                    evidence.get("latest_smoke_model"),
                    evidence.get("latest_replay_summary"),
                    evidence.get("latest_smoke_gate"),
                    evidence.get("latest_smoke_matrix"),
                ]
                if value
            ],
        })
    return sorted(queue, key=lambda item: (item["priority"], item["candidate_id"]))


def _decision_queue_priority(gate_status: str) -> int:
    return {
        "integration_blocked": 10,
        "integration_reviewed": 20,
        "watch_only_prescreen_ready": 30,
        "watch_only_blocked": 40,
        "watch_only_monitoring": 50,
        "registered_no_review": 60,
        "production_baseline": 90,
    }.get(gate_status, 80)


def _decision_queue_status(gate_status: str) -> str:
    return {
        "production_baseline": "baseline_protected",
        "integration_blocked": "blocked_needs_evidence",
        "integration_reviewed": "operator_review_required",
        "watch_only_prescreen_ready": "operator_priority_review",
        "watch_only_blocked": "watch_only_blocked",
        "watch_only_monitoring": "watch_only_monitoring",
        "registered_no_review": "registered_no_review",
    }.get(gate_status, "operator_review_required")


def _decision_queue_action(
    *,
    candidate_id: str,
    gate_status: str,
    required_next_gate: str,
) -> str:
    if candidate_id == "openclaw_incumbent":
        return "keep_openclaw_as_production_decision_core_until_formal_replacement_adr"
    if required_next_gate:
        return required_next_gate
    if gate_status == "registered_no_review":
        return "add_to_primary_source_watch_before_any_integration_review"
    return "continue_weekly_primary_source_market_watch"


def _decision_approval_boundary(
    *,
    candidate_id: str,
    gate_status: str,
    integration: dict[str, Any],
    promotion: dict[str, Any],
) -> dict[str, bool]:
    approval_boundary = integration.get("approval_boundary") or {}
    classification = promotion.get("classification") or {}
    risk_flags = {str(flag) for flag in classification.get("risk_flags") or []}
    is_baseline = candidate_id == "openclaw_incumbent"
    is_watch_only = gate_status.startswith("watch_only") or gate_status == "registered_no_review"
    requires_dependency = bool(
        approval_boundary.get("requires_dependency_approval")
        or "requires_dependency_boundary_review" in risk_flags
    )
    requires_paid_api = bool(
        approval_boundary.get("requires_cost_approval")
        or "likely_requires_paid_provider_boundary_review" in risk_flags
    )
    return {
        "replacement_adr_required": True,
        "priority_upgrade_required": is_watch_only,
        "market_scorecard_update_required": is_watch_only,
        "replay_approval_required": not is_baseline,
        "sdk_install_approval_required": requires_dependency or not is_baseline,
        "paid_api_approval_required": requires_paid_api,
        "shadow_or_canary_approval_required": not is_baseline,
        "production_routing_approval_required": True,
    }


def _decision_risk_notes(
    *,
    candidate_id: str,
    integration: dict[str, Any],
    promotion: dict[str, Any],
    operator_blockers: list[Any],
) -> list[str]:
    notes = []
    if candidate_id == "openclaw_incumbent":
        notes.append("no_candidate_has_formal_replacement_approval")

    market_score = integration.get("market_score") or {}
    notes.extend(str(value) for value in market_score.get("risks") or [])

    classification = promotion.get("classification") or {}
    notes.extend(str(value) for value in classification.get("risk_flags") or [])
    notes.extend(str(value) for value in operator_blockers)
    return list(dict.fromkeys(notes))[:6]


def _approval_summary(*reports: dict[str, Any]) -> dict[str, int]:
    keys = {
        "priority_upgrades_approved": [
            ("summary", "priority_upgrades_approved"),
        ],
        "market_scorecard_updates_approved": [
            ("summary", "market_scorecard_updates_approved"),
        ],
        "replay_candidates_approved": [
            ("summary", "replay_candidates_approved"),
        ],
        "sdk_installations_approved": [
            ("summary", "sdk_installations_approved"),
        ],
        "paid_api_calls_approved": [
            ("summary", "paid_api_calls_approved"),
        ],
        "production_changes_approved": [
            ("summary", "production_changes_approved"),
        ],
        "shadow_or_canary_approved": [
            ("summary", "shadow_or_canary_approved"),
        ],
        "replacement_decisions_approved": [
            ("policy", "replacement_decision_allowed"),
        ],
    }
    result = {}
    for output_key, paths in keys.items():
        total = 0
        for report in reports:
            for section, key in paths:
                value = (report.get(section) or {}).get(key)
                if isinstance(value, bool):
                    total += 1 if value else 0
                elif isinstance(value, int):
                    total += value
        result[output_key] = total
    return result


def _candidate_gate_status(
    *,
    candidate_id: str,
    is_watch_only: bool,
    integration: dict[str, Any],
    promotion: dict[str, Any],
) -> str:
    if candidate_id == "openclaw_incumbent":
        return "production_baseline"
    if promotion:
        if promotion.get("eligible_for_market_scorecard_prescreen"):
            return "watch_only_prescreen_ready"
        return "watch_only_blocked"
    if integration:
        decision = str(integration.get("decision", ""))
        if decision.startswith("do_not_integrate") or "blocked" in decision:
            return "integration_blocked"
        return "integration_reviewed"
    if is_watch_only:
        return "watch_only_monitoring"
    return "registered_no_review"


def _candidate_current_gate(
    *,
    is_baseline: bool,
    candidate: dict[str, Any],
    integration: dict[str, Any],
    promotion: dict[str, Any],
    readiness: dict[str, Any],
) -> str:
    if is_baseline:
        return "production_decision_core"
    return str(
        promotion.get("integration_stage")
        or readiness.get("stage")
        or candidate.get("required_stage")
        or ""
    )


def _candidate_required_next_gate(
    *,
    is_baseline: bool,
    integration: dict[str, Any],
    promotion: dict[str, Any],
    readiness: dict[str, Any],
) -> str:
    if is_baseline:
        return "formal_replacement_adr_and_promotion_gate_required"
    return str(
        promotion.get("required_next_gate")
        or readiness.get("allowed_next_gate")
        or integration.get("decision")
        or "continue_weekly_primary_source_market_watch"
    )


def _market_score(integration: dict[str, Any]) -> float | None:
    market_score = integration.get("market_score") or {}
    value = market_score.get("total_score")
    if isinstance(value, int | float):
        return round(float(value), 4)
    return None


def _candidate_operator_blockers(
    *,
    integration: dict[str, Any],
    promotion: dict[str, Any],
) -> list[str]:
    blockers = []
    for value in promotion.get("blockers") or []:
        blockers.append(str(value))
    for value in integration.get("unblock_conditions") or []:
        blockers.append(str(value))
    return blockers


def _next_allowed_actions(candidate_groups: dict[str, list[str]]) -> list[str]:
    actions = ["continue_weekly_primary_source_market_watch"]
    if candidate_groups["watch_only_scorecard_prescreen_ready"]:
        actions.append("operator_may_review_priority_upgrade_for_watch_only_candidates")
    if candidate_groups["replay_or_integration_blocked"]:
        actions.append("rerun_existing_replay_only_after_evidence_or_adapter_change")
    return actions


def _evaluation_cadence(generated_at: str) -> dict[str, Any]:
    return {
        "workflow": _MARKET_WATCH_WORKFLOW,
        "schedule": "weekly_monday_0900_asia_taipei",
        "timezone": "Asia/Taipei",
        "next_scheduled_run_at": _next_monday_0900_taipei(generated_at),
        "trigger_modes": [
            "scheduled_weekly",
            "manual_dispatch",
            "operator_triggered_after_primary_source_signal",
        ],
        "primary_source_policy": "primary_sources_only_no_llm_no_sdk_no_paid_api",
        "operator_review_gate": (
            "priority_upgrade_required_before_scorecard_replay_sdk_api_shadow_canary_or_production"
        ),
    }


def _market_watch_health(
    *,
    summary: dict[str, int],
    cadence: dict[str, Any],
) -> dict[str, Any]:
    blockers = []
    if summary["source_failures"] > 0:
        blockers.append("source_failures_present")
    if summary["recommended_watch_additions_remaining"] > 0:
        blockers.append("unclassified_discovery_watch_additions_remaining")
    if summary["integration_queue_count"] > 0:
        blockers.append("integration_queue_not_empty")

    status = "healthy" if not blockers else "blocked"
    stale_after = _stale_after(cadence["next_scheduled_run_at"])
    return {
        "status": status,
        "freshness_sla_hours": _FRESHNESS_SLA_HOURS,
        "stale_grace_hours": _STALE_GRACE_HOURS,
        "stale_after": stale_after,
        "source_failures_block_priority_upgrade": summary["source_failures"] > 0,
        "blocked_from_integration": summary["blocked_from_integration"],
        "operator_blockers": blockers,
    }


def _stale_after(next_scheduled_run_at: str) -> str:
    parsed = datetime.fromisoformat(next_scheduled_run_at.replace("Z", "+00:00"))
    if parsed.tzinfo is None:
        parsed = parsed.replace(tzinfo=_TAIPEI_TZ)
    return (parsed.astimezone(_TAIPEI_TZ) + timedelta(hours=_STALE_GRACE_HOURS)).isoformat()


def _next_monday_0900_taipei(generated_at: str) -> str:
    parsed = datetime.fromisoformat(generated_at.replace("Z", "+00:00"))
    if parsed.tzinfo is None:
        parsed = parsed.replace(tzinfo=timezone.utc)
    local = parsed.astimezone(_TAIPEI_TZ)
    days_until_monday = (0 - local.weekday()) % 7
    candidate_date = local.date() + timedelta(days=days_until_monday)
    scheduled = datetime.combine(candidate_date, time(9, 0), tzinfo=_TAIPEI_TZ)
    if scheduled <= local:
        scheduled += timedelta(days=7)
    return scheduled.isoformat()


def _is_watch_only(candidate: dict[str, Any]) -> bool:
    return (
        candidate.get("evaluation_priority") == "watch_only"
        or candidate.get("required_stage") == "watch_only_primary_source_monitoring"
    )


def _require_schema(report: dict[str, Any], expected: str, name: str) -> None:
    if report.get("schema_version") != expected:
        raise ValueError(f"{name} must be {expected}")
