"""
AI Agent Gitea PR draft lane snapshot.

Loads the latest committed, read-only policy for AI Agent generated Gitea PR
draft plans. This module never pushes branches, creates PRs, edits workflows,
writes lockfiles, upgrades packages, triggers CI, sends Telegram messages, or
exposes work-window transcripts.
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from src.services.snapshot_paths import default_evaluations_dir

_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__))
_SNAPSHOT_PATTERN = "ai_agent_gitea_pr_draft_lane_*.json"
_SCHEMA_VERSION = "ai_agent_gitea_pr_draft_lane_v1"
_RUNTIME_AUTHORITY = "draft_lane_only_no_pr_creation_or_branch_push"
_TRANSCRIPT_MARKERS = {
    "# In app browser",
    "My request for Codex",
    "Current URL:",
    "AGENTS.md instructions",
    "<environment_context>",
    "批准！繼續",
}


def load_latest_ai_agent_gitea_pr_draft_lane(
    evaluations_dir: Path | None = None,
) -> dict[str, Any]:
    """Load the newest committed AI Agent Gitea PR draft lane policy."""
    directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR
    candidates = sorted(directory.glob(_SNAPSHOT_PATTERN))
    if not candidates:
        raise FileNotFoundError(f"no AI Agent Gitea PR draft lane 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, _SCHEMA_VERSION, str(latest))
    _require_read_only_boundaries(payload, str(latest))
    _require_rollup_consistency(payload, str(latest))
    _require_grouping_and_checks(payload, str(latest))
    _require_owner_and_rollback_contracts(payload, str(latest))
    _require_template_redaction(payload, str(latest))
    _require_no_plaintext_secret_payload_keys(payload, str(latest))
    _require_no_conversation_transcript_content(payload, str(latest))
    return payload


def _require_schema(payload: dict[str, Any], expected: str, label: str) -> None:
    actual = payload.get("schema_version")
    if actual != expected:
        raise ValueError(f"{label}: expected schema_version={expected}, got {actual!r}")


def _require_read_only_boundaries(payload: dict[str, Any], label: str) -> None:
    program_status = payload.get("program_status") or {}
    if program_status.get("read_only_mode") is not True:
        raise ValueError(f"{label}: program_status.read_only_mode must be true")
    if program_status.get("runtime_authority") != _RUNTIME_AUTHORITY:
        raise ValueError(f"{label}: runtime_authority must stay {_RUNTIME_AUTHORITY}")

    operation_boundaries = payload.get("operation_boundaries") or {}
    if operation_boundaries.get("read_only_lane_allowed") is not True:
        raise ValueError(f"{label}: read_only_lane_allowed must be true")

    blocked_operation_flags = {
        "gitea_branch_push_allowed",
        "gitea_pr_creation_allowed",
        "gitea_pr_update_allowed",
        "gitea_pr_comment_allowed",
        "auto_merge_allowed",
        "workflow_trigger_allowed",
        "ci_workflow_change_allowed",
        "lockfile_write_allowed",
        "package_upgrade_allowed",
        "file_mutation_allowed",
        "external_registry_lookup_allowed",
        "vulnerability_database_download_allowed",
        "docker_build_allowed",
        "image_pull_allowed",
        "production_route_change_allowed",
        "telegram_direct_send_allowed",
        "telegram_gateway_queue_write_allowed",
        "secret_plaintext_allowed",
        "conversation_transcript_allowed",
    }
    allowed_operation_flags = sorted(
        flag
        for flag in blocked_operation_flags
        if operation_boundaries.get(flag) is not False
    )
    if allowed_operation_flags:
        raise ValueError(
            f"{label}: operation boundaries must remain false: {allowed_operation_flags}"
        )

    approval_boundaries = payload.get("approval_boundaries") or {}
    allowed_approval_flags = sorted(
        flag for flag, value in approval_boundaries.items() if value is not False
    )
    if allowed_approval_flags:
        raise ValueError(
            f"{label}: approval boundaries must remain false: {allowed_approval_flags}"
        )


def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None:
    grouping_rules = payload.get("grouping_rules") or []
    lane_steps = payload.get("lane_steps") or []
    required_checks = payload.get("required_checks") or []
    owner_requirements = payload.get("owner_response_requirements") or []
    rollback_requirements = payload.get("rollback_requirements") or []
    templates = payload.get("draft_templates") or []
    rollups = payload.get("rollups") or {}

    expected_counts = {
        "grouping_rule_count": len(grouping_rules),
        "lane_step_count": len(lane_steps),
        "required_check_count": len(required_checks),
        "owner_response_requirement_count": len(owner_requirements),
        "rollback_requirement_count": len(rollback_requirements),
        "draft_template_count": len(templates),
    }
    mismatched = {
        key: {"expected": expected, "actual": rollups.get(key)}
        for key, expected in expected_counts.items()
        if rollups.get(key) != expected
    }
    if mismatched:
        raise ValueError(f"{label}: rollup counts must match payload sections: {mismatched}")

    expected_group_ids = sorted(rule.get("group_id") for rule in grouping_rules)
    if sorted(rollups.get("draft_group_ids") or []) != expected_group_ids:
        raise ValueError(f"{label}: rollups.draft_group_ids mismatch")

    expected_owner_ids = sorted(
        requirement.get("requirement_id") for requirement in owner_requirements
    )
    if sorted(rollups.get("owner_response_requirement_ids") or []) != expected_owner_ids:
        raise ValueError(f"{label}: rollups.owner_response_requirement_ids mismatch")

    zero_rollups = {
        "gitea_branch_push_allowed_count",
        "gitea_pr_creation_allowed_count",
        "auto_merge_allowed_count",
        "workflow_trigger_allowed_count",
        "lockfile_write_allowed_count",
        "telegram_direct_send_allowed_count",
        "conversation_transcript_allowed_count",
    }
    nonzero = sorted(key for key in zero_rollups if rollups.get(key) != 0)
    if nonzero:
        raise ValueError(f"{label}: draft lane safety counters must remain 0: {nonzero}")


def _require_grouping_and_checks(payload: dict[str, Any], label: str) -> None:
    unsafe_groups = [
        rule.get("group_id")
        for rule in payload.get("grouping_rules") or []
        if rule.get("draft_only") is not True
        or rule.get("automerge") is not False
        or rule.get("requires_openclaw_review") is not True
        or rule.get("rollback_required") is not True
        or not rule.get("required_check_ids")
        or not isinstance(rule.get("max_batch_size"), int)
        or rule.get("max_batch_size", 0) < 1
    ]
    if unsafe_groups:
        raise ValueError(f"{label}: grouping rules must stay draft-only and gated: {unsafe_groups}")

    check_ids = {check.get("check_id") for check in payload.get("required_checks") or []}
    unknown_check_refs = sorted(
        {
            check_id
            for rule in payload.get("grouping_rules") or []
            for check_id in rule.get("required_check_ids") or []
            if check_id not in check_ids
        }
    )
    if unknown_check_refs:
        raise ValueError(f"{label}: grouping rules reference unknown checks: {unknown_check_refs}")

    unsafe_checks = [
        check.get("check_id")
        for check in payload.get("required_checks") or []
        if check.get("blocking") is not True
        or check.get("evidence_required") is not True
        or check.get("run_now_allowed") is not False
    ]
    if unsafe_checks:
        raise ValueError(f"{label}: required checks must be blocking evidence-only: {unsafe_checks}")

    unsafe_steps = [
        step.get("step_id")
        for step in payload.get("lane_steps") or []
        if step.get("runtime_execution_allowed") is not False
        or step.get("repo_write_allowed") is not False
        or not step.get("planned_output")
    ]
    if unsafe_steps:
        raise ValueError(f"{label}: lane steps must remain read-only plans: {unsafe_steps}")


def _require_owner_and_rollback_contracts(payload: dict[str, Any], label: str) -> None:
    required_owner_fields = {
        "owner",
        "decision",
        "business_impact",
        "risk_acceptance",
        "rollback_acceptance",
        "maintenance_window",
        "evidence_ref",
    }
    actual_owner_fields = {
        field
        for requirement in payload.get("owner_response_requirements") or []
        for field in requirement.get("required_fields") or []
    }
    if not required_owner_fields.issubset(actual_owner_fields):
        raise ValueError(f"{label}: owner response requirements missing required fields")

    unsafe_rollback = [
        item.get("requirement_id")
        for item in payload.get("rollback_requirements") or []
        if item.get("required") is not True
        or item.get("must_be_attached_before_pr_creation") is not True
    ]
    if unsafe_rollback:
        raise ValueError(f"{label}: rollback requirements must be attached before PR: {unsafe_rollback}")


def _require_template_redaction(payload: dict[str, Any], label: str) -> None:
    forbidden_fields = {
        "secret_value",
        "token",
        "authorization_header",
        "work_window_transcript",
        "codex_user_message",
        "prompt_text",
        "chain_of_thought",
        "session_id",
        "browser_context",
    }
    for template in payload.get("draft_templates") or []:
        template_id = template.get("template_id")
        if template.get("automerge") is not False:
            raise ValueError(f"{label}: draft template must keep automerge=false: {template_id}")
        if template.get("branch_push_allowed") is not False:
            raise ValueError(f"{label}: draft template must not allow branch push: {template_id}")
        if not forbidden_fields.issubset(set(template.get("forbidden_fields") or [])):
            raise ValueError(f"{label}: draft template missing redaction fields: {template_id}")

    display = payload.get("display_redaction_contract") or {}
    if display.get("conversation_transcript_display_allowed") is not False:
        raise ValueError(f"{label}: conversation transcript display must remain false")
    if display.get("redaction_required") is not True:
        raise ValueError(f"{label}: display redaction must be required")


def _require_no_plaintext_secret_payload_keys(value: Any, label: str, path: str = "$") -> None:
    if isinstance(value, dict):
        forbidden_key_fragments = {
            "secret_value",
            "token_plaintext",
            "authorization_header",
            "private_key",
            "credential_value",
        }
        for key, nested in value.items():
            normalized_key = str(key).lower()
            if any(fragment in normalized_key for fragment in forbidden_key_fragments):
                raise ValueError(f"{label}: forbidden plaintext secret key at {path}.{key}")
            _require_no_plaintext_secret_payload_keys(nested, label, f"{path}.{key}")
    elif isinstance(value, list):
        for index, nested in enumerate(value):
            _require_no_plaintext_secret_payload_keys(nested, label, f"{path}[{index}]")


def _require_no_conversation_transcript_content(value: Any, label: str, path: str = "$") -> None:
    if isinstance(value, str):
        for marker in _TRANSCRIPT_MARKERS:
            if marker in value:
                raise ValueError(
                    f"{label}: forbidden work-window conversation content at {path}: {marker}"
                )
    elif isinstance(value, dict):
        for key, nested in value.items():
            _require_no_conversation_transcript_content(nested, label, f"{path}.{key}")
    elif isinstance(value, list):
        for index, nested in enumerate(value):
            _require_no_conversation_transcript_content(nested, label, f"{path}[{index}]")
