"""
IwoooS Wazuh manager registry reviewer validation readback.

This service exposes a committed reviewer-validation contract and a no-persist
validator for owner-provided redacted Wazuh manager registry exports. It never
queries Wazuh, reads host data, reads secrets, persists raw payloads, or
authorizes runtime actions.
"""

from __future__ import annotations

import json
import re
from pathlib import Path
from typing import Any

from src.services.snapshot_paths import default_security_dir

_DEFAULT_SECURITY_DIR = default_security_dir(Path(__file__))
_SNAPSHOT_FILE = "wazuh-manager-registry-reviewer-validation.snapshot.json"
_EXPECTED_SCHEMA = "wazuh_manager_registry_reviewer_validation_v1"

_REQUIRED_FALSE_BOUNDARIES = {
    "agent_identity_public_display_allowed",
    "host_write_authorized",
    "internal_ip_public_display_allowed",
    "kali_active_scan_authorized",
    "raw_wazuh_payload_storage_allowed",
    "runtime_execution_authorized",
    "secret_value_collection_allowed",
    "wazuh_active_response_authorized",
    "wazuh_agent_reenroll_authorized",
    "wazuh_agent_restart_authorized",
    "wazuh_api_live_query_authorized",
    "wazuh_manager_restart_authorized",
}

_SENSITIVE_TEXT_PATTERNS = {
    "internal_ip": re.compile(r"\b(?:10|127|172\.(?:1[6-9]|2\d|3[01])|192\.168)\.\d{1,3}\.\d{1,3}\b"),
    "authorization_header": re.compile(r"Authorization\s*:", re.IGNORECASE),
    "bearer_token": re.compile(r"Bearer\s+[A-Za-z0-9._-]{10,}", re.IGNORECASE),
    "basic_auth": re.compile(r"Basic\s+[A-Za-z0-9+/=]{10,}", re.IGNORECASE),
    "password_assignment": re.compile(r"password\s*[:=]\s*['\"][^'\"]+['\"]", re.IGNORECASE),
    "token_assignment": re.compile(r"token\s*[:=]\s*['\"][^'\"]+['\"]", re.IGNORECASE),
    "cookie_assignment": re.compile(r"cookie\s*[:=]\s*['\"][^'\"]+['\"]", re.IGNORECASE),
    "client_keys": re.compile(r"client\.keys", re.IGNORECASE),
    "private_key": re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----"),
    "work_window_text": re.compile(r"(工作視窗|批准！繼續|source_thread_id|owenhytsai/)", re.IGNORECASE),
}

_FORBIDDEN_KEY_FRAGMENTS = {
    "authorization_header",
    "basic_auth",
    "bearer_token",
    "client_keys",
    "cookie",
    "dashboard_api_secret",
    "firewall_change",
    "full_cli_output",
    "full_journal",
    "host_write",
    "hostname",
    "internal_ip",
    "nginx_reload",
    "password",
    "private_key",
    "raw_dashboard_request",
    "raw_log",
    "raw_wazuh_payload",
    "stored_api_password",
    "unredacted_screenshot",
}

_RUNTIME_ACTION_KEYS = {
    "active_response_enable",
    "agent_reenroll",
    "agent_restart",
    "argocd_sync",
    "firewall_change",
    "host_write",
    "k8s_or_argocd_change",
    "kali_active_scan",
    "nginx_reload",
    "runtime_execution_authorized",
    "secret_rotation",
    "wazuh_active_response",
    "wazuh_agent_reenroll",
    "wazuh_agent_restart",
    "wazuh_api_live_query",
    "wazuh_dashboard_secret_patch",
    "wazuh_manager_restart",
}

_DASHBOARD_REQUIRED_FIELDS = {
    "dashboard_api_connection_check_status",
    "dashboard_api_version_check_status",
    "dashboard_index_pattern_statuses",
    "dashboard_api_degradation_root_cause",
    "dashboard_api_repair_postcheck_ref",
}

_READONLY_CREDENTIAL_REQUIRED_FIELDS = {
    "collection_method",
    "manager_health_ref",
    "redacted_evidence_refs",
}

_ACCOUNTABILITY_REQUIRED_FIELDS = {
    "followup_owner",
    "rollback_owner",
    "postcheck_plan",
}

_ACCEPTANCE_EVIDENCE_REQUIRED_FIELDS = {
    "acceptance_reviewer_role",
    "acceptance_decision",
    "acceptance_decision_reason",
    "accepted_scope_aliases",
    "accepted_agent_total",
    "accepted_agent_active",
    "accepted_agent_disconnected",
    "accepted_agent_never_connected",
    "manager_registry_acceptance_evidence_refs",
    "post_enable_readback_ref",
    "product_scope_parity_ref",
    "reviewed_at",
    "followup_owner",
    "rollback_owner",
    "post_acceptance_verifier_plan",
}


def load_latest_iwooos_wazuh_manager_registry_reviewer_validation(
    security_dir: Path | None = None,
) -> dict[str, Any]:
    """Load the public-safe Wazuh manager registry reviewer-validation contract."""
    directory = security_dir or _DEFAULT_SECURITY_DIR
    snapshot = _load_snapshot(directory)
    _require_boundaries(snapshot)

    summary = _summary(snapshot)
    merged_summary = {
        "expected_scope_alias_count": _int(summary.get("expected_scope_alias_count")),
        "required_owner_field_count": _int(summary.get("required_owner_field_count")),
        "per_host_required_field_count": _int(summary.get("per_host_required_field_count")),
        "reviewer_validation_check_count": _int(summary.get("reviewer_validation_check_count")),
        "outcome_lane_count": _int(summary.get("outcome_lane_count")),
        "evidence_slot_count": _int(summary.get("evidence_slot_count")),
        "forbidden_payload_count": _int(summary.get("forbidden_payload_count")),
        "forbidden_action_count": _int(summary.get("forbidden_action_count")),
        "owner_registry_export_received_count": _int(summary.get("owner_registry_export_received_count")),
        "owner_registry_export_accepted_count": _int(summary.get("owner_registry_export_accepted_count")),
        "reviewer_validation_ready_count": _int(summary.get("reviewer_validation_ready_count")),
        "reviewer_validation_passed_count": _int(summary.get("reviewer_validation_passed_count")),
        "reviewer_validation_failed_count": _int(summary.get("reviewer_validation_failed_count")),
        "reviewer_validation_quarantined_count": _int(summary.get("reviewer_validation_quarantined_count")),
        "manager_registry_accepted_count": _int(summary.get("manager_registry_accepted_count")),
        "manager_registry_acceptance_intake_endpoint_available_count": _int(
            summary.get("manager_registry_acceptance_intake_endpoint_available_count")
        ),
        "manager_registry_acceptance_evidence_received_count": _int(
            summary.get("manager_registry_acceptance_evidence_received_count")
        ),
        "manager_registry_acceptance_evidence_review_ready_count": _int(
            summary.get("manager_registry_acceptance_evidence_review_ready_count")
        ),
        "post_enable_readback_passed_count": _int(summary.get("post_enable_readback_passed_count")),
        "runtime_gate_count": _int(summary.get("runtime_gate_count")),
        "host_write_authorized_count": _int(summary.get("host_write_authorized_count")),
        "active_response_authorized_count": _int(summary.get("active_response_authorized_count")),
        "secret_value_collection_allowed_count": _int(summary.get("secret_value_collection_allowed_count")),
    }

    return {
        "schema_version": "iwooos_wazuh_manager_registry_reviewer_validation_readback_v1",
        "source_schema_version": snapshot["schema_version"],
        "status": snapshot.get("status", "waiting_owner_registry_export_for_reviewer_validation"),
        "mode": snapshot.get("mode", "committed_validation_contract_readback_no_runtime_no_secret_collection"),
        "source_refs": [
            f"docs/security/{_SNAPSHOT_FILE}",
            "scripts/security/wazuh-manager-registry-reviewer-validation.py",
        ],
        "owner_export_validation_endpoint": (
            "/api/v1/iwooos/wazuh-manager-registry-reviewer-validation/validate-owner-export"
        ),
        "owner_export_validation_mode": "no_persist_validation_no_runtime_action",
        "manager_registry_acceptance_validation_endpoint": (
            "/api/v1/iwooos/wazuh-manager-registry-reviewer-validation/validate-manager-registry-acceptance"
        ),
        "manager_registry_acceptance_validation_mode": "no_persist_acceptance_evidence_review_no_runtime_action",
        "summary": merged_summary,
        "expected_scope_aliases": _strings(snapshot.get("expected_scope_aliases")),
        "reviewer_validation_checks": _checks(snapshot.get("reviewer_validation_checks")),
        "outcome_lanes": _strings(snapshot.get("outcome_lanes")),
        "evidence_slots": _evidence_slots(snapshot.get("evidence_slots")),
        "forbidden_payloads": _strings(snapshot.get("forbidden_payloads")),
        "forbidden_actions": _strings(snapshot.get("forbidden_actions")),
        "boundary_markers": _boundary_markers(merged_summary),
        "boundaries": {
            "wazuh_api_live_query_authorized": False,
            "wazuh_agent_reenroll_authorized": False,
            "wazuh_agent_restart_authorized": False,
            "wazuh_manager_restart_authorized": False,
            "wazuh_active_response_authorized": False,
            "host_write_authorized": False,
            "secret_value_collection_allowed": False,
            "raw_wazuh_payload_storage_allowed": False,
            "agent_identity_public_display_allowed": False,
            "internal_ip_public_display_allowed": False,
            "kali_active_scan_authorized": False,
            "runtime_execution_authorized": False,
            "not_authorization": True,
        },
        "no_false_green_rules": _strings(snapshot.get("no_false_green_rules")),
    }


def _load_snapshot(directory: Path) -> dict[str, Any]:
    path = directory / _SNAPSHOT_FILE
    if not path.is_file():
        raise FileNotFoundError(f"{path}: Wazuh manager registry reviewer validation 快照不存在")
    with path.open(encoding="utf-8") as handle:
        payload = json.load(handle)
    if not isinstance(payload, dict):
        raise ValueError(f"{path}: expected JSON object")
    if payload.get("schema_version") != _EXPECTED_SCHEMA:
        raise ValueError(f"{path}: expected schema_version={_EXPECTED_SCHEMA}")
    return payload


def _summary(payload: dict[str, Any]) -> dict[str, Any]:
    summary = payload.get("summary")
    return summary if isinstance(summary, dict) else {}


def _int(value: Any) -> int:
    return value if isinstance(value, int) else 0


def _strings(value: Any) -> list[str]:
    if not isinstance(value, list):
        return []
    return [item for item in value if isinstance(item, str)]


def _checks(value: Any) -> list[dict[str, str]]:
    if not isinstance(value, list):
        return []
    checks: list[dict[str, str]] = []
    for item in value:
        if not isinstance(item, dict):
            continue
        checks.append(
            {
                "check_id": str(item.get("check_id", "")),
                "title": str(item.get("title", "")),
                "required_evidence": str(item.get("required_evidence", "")),
                "failure_lane": str(item.get("failure_lane", "")),
            }
        )
    return checks


def _evidence_slots(value: Any) -> list[dict[str, Any]]:
    if not isinstance(value, list):
        return []
    slots: list[dict[str, Any]] = []
    for item in value:
        if not isinstance(item, dict):
            continue
        slots.append(
            {
                "slot_id": str(item.get("slot_id", "")),
                "title": str(item.get("title", "")),
                "required_fields": _strings(item.get("required_fields")),
                "received": item.get("received") is True,
                "accepted": item.get("accepted") is True,
                "quarantined": item.get("quarantined") is True,
                "next_gate": str(item.get("next_gate", "")),
            }
        )
    return slots


def _boundary_markers(summary: dict[str, int]) -> list[str]:
    return [
        "wazuh_manager_registry_reviewer_validation_visible=true",
        "wazuh_manager_registry_owner_export_validation_api_available=true",
        f"wazuh_manager_registry_reviewer_validation_expected_scope_alias_count={summary['expected_scope_alias_count']}",
        f"wazuh_manager_registry_reviewer_validation_required_owner_field_count={summary['required_owner_field_count']}",
        f"wazuh_manager_registry_reviewer_validation_per_host_required_field_count={summary['per_host_required_field_count']}",
        f"wazuh_manager_registry_reviewer_validation_check_count={summary['reviewer_validation_check_count']}",
        f"wazuh_manager_registry_reviewer_validation_outcome_lane_count={summary['outcome_lane_count']}",
        f"wazuh_manager_registry_reviewer_validation_evidence_slot_count={summary['evidence_slot_count']}",
        f"wazuh_manager_registry_reviewer_validation_forbidden_payload_count={summary['forbidden_payload_count']}",
        f"wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count={summary['owner_registry_export_received_count']}",
        f"wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count={summary['owner_registry_export_accepted_count']}",
        f"wazuh_manager_registry_reviewer_validation_passed_count={summary['reviewer_validation_passed_count']}",
        f"wazuh_manager_registry_reviewer_validation_quarantined_count={summary['reviewer_validation_quarantined_count']}",
        f"wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count={summary['manager_registry_accepted_count']}",
        "wazuh_manager_registry_acceptance_validation_api_available=true",
        f"wazuh_manager_registry_acceptance_evidence_received_count={summary['manager_registry_acceptance_evidence_received_count']}",
        f"wazuh_manager_registry_acceptance_evidence_review_ready_count={summary['manager_registry_acceptance_evidence_review_ready_count']}",
        f"wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count={summary['post_enable_readback_passed_count']}",
        f"wazuh_manager_registry_reviewer_validation_runtime_gate_count={summary['runtime_gate_count']}",
        "wazuh_api_live_query_authorized=false",
        "wazuh_agent_reenroll_authorized=false",
        "wazuh_agent_restart_authorized=false",
        "wazuh_active_response_authorized=false",
        "host_write_authorized=false",
        "raw_wazuh_payload_storage_allowed=false",
        "secret_value_collection_allowed=false",
        "not_authorization=true",
    ]


def _require_boundaries(payload: dict[str, Any]) -> None:
    summary = _summary(payload)
    for key in (
        "runtime_gate_count",
        "host_write_authorized_count",
        "active_response_authorized_count",
        "secret_value_collection_allowed_count",
    ):
        if _int(summary.get(key)) != 0:
            raise ValueError(f"Wazuh manager registry reviewer validation summary.{key} 必須維持 0")

    expected_aliases = _int(summary.get("expected_scope_alias_count"))
    manager_accepted = _int(summary.get("manager_registry_accepted_count"))
    received = _int(summary.get("owner_registry_export_received_count"))
    accepted = _int(summary.get("owner_registry_export_accepted_count"))
    ready = _int(summary.get("reviewer_validation_ready_count"))
    passed = _int(summary.get("reviewer_validation_passed_count"))
    failed = _int(summary.get("reviewer_validation_failed_count"))
    quarantined = _int(summary.get("reviewer_validation_quarantined_count"))
    post_enable = _int(summary.get("post_enable_readback_passed_count"))
    acceptance_endpoint = _int(summary.get("manager_registry_acceptance_intake_endpoint_available_count"))
    acceptance_received = _int(summary.get("manager_registry_acceptance_evidence_received_count"))
    acceptance_ready = _int(summary.get("manager_registry_acceptance_evidence_review_ready_count"))
    if any(
        value < 0
        for value in (
            received,
            accepted,
            ready,
            passed,
            failed,
            quarantined,
            post_enable,
            acceptance_endpoint,
            acceptance_received,
            acceptance_ready,
            manager_accepted,
        )
    ):
        raise ValueError("Wazuh manager registry reviewer validation counters 不得為負數")
    if manager_accepted > expected_aliases:
        raise ValueError("manager_registry_accepted_count 不得大於 expected_scope_alias_count")
    if accepted > received:
        raise ValueError("owner_registry_export_accepted_count 不得大於 received_count")
    if ready > received:
        raise ValueError("reviewer_validation_ready_count 不得大於 received_count")
    if passed > accepted:
        raise ValueError("reviewer_validation_passed_count 不得大於 accepted_count")
    if post_enable > passed:
        raise ValueError("post_enable_readback_passed_count 不得大於 reviewer_validation_passed_count")
    if acceptance_endpoint > 1:
        raise ValueError("manager_registry_acceptance_intake_endpoint_available_count 不得大於 1")
    if acceptance_ready > acceptance_received:
        raise ValueError(
            "manager_registry_acceptance_evidence_review_ready_count 不得大於 received_count"
        )
    if manager_accepted and not all(
        value > 0
        for value in (
            received,
            accepted,
            ready,
            passed,
            post_enable,
            acceptance_endpoint,
            acceptance_received,
            acceptance_ready,
        )
    ):
        raise ValueError("manager_registry_accepted_count 上修前必須先有完整 committed acceptance evidence")
    if failed and passed:
        raise ValueError("reviewer_validation_failed_count 與 passed_count 不得同時為正")
    if quarantined and accepted:
        raise ValueError("reviewer_validation_quarantined_count 與 accepted_count 不得同時為正")

    boundaries = payload.get("execution_boundaries")
    if not isinstance(boundaries, dict):
        raise ValueError("Wazuh manager registry reviewer validation execution_boundaries 缺失")
    for key in _REQUIRED_FALSE_BOUNDARIES:
        if boundaries.get(key) is not False:
            raise ValueError(f"Wazuh manager registry reviewer validation execution_boundaries.{key} 必須維持 false")
    if boundaries.get("not_authorization") is not True:
        raise ValueError("Wazuh manager registry reviewer validation not_authorization 必須維持 true")


def validate_iwooos_wazuh_manager_registry_owner_export(
    owner_export: dict[str, Any],
    security_dir: Path | None = None,
) -> dict[str, Any]:
    """Validate one redacted owner export without persisting it or changing runtime truth."""
    contract = load_latest_iwooos_wazuh_manager_registry_reviewer_validation(security_dir)
    snapshot = _load_snapshot(security_dir or _DEFAULT_SECURITY_DIR)
    expected_aliases = set(_strings(snapshot.get("expected_scope_aliases")))
    required_owner_fields = _strings(snapshot.get("required_owner_fields"))
    per_host_required_fields = _strings(snapshot.get("per_host_required_fields"))

    findings: list[dict[str, Any]] = []
    evidence_status = _initial_evidence_status(snapshot)

    if not isinstance(owner_export, dict):
        findings.append(_finding("RV-01", "blocker", "request_missing_fields", "owner export 必須是 JSON object", []))
        return _validation_result(contract, "request_missing_fields", findings, evidence_status)

    sensitive_hits = _collect_sensitive_hits(owner_export)
    if sensitive_hits:
        findings.append(
            _finding(
                "RV-07",
                "critical",
                "quarantine_sensitive_payload",
                "owner export 含禁止內容或疑似未脫敏內容，已進隔離分流；回應不回傳原始值。",
                [hit["path"] for hit in sensitive_hits[:12]],
                {"categories": sorted({hit["category"] for hit in sensitive_hits})},
            )
        )
        return _validation_result(contract, "quarantine_sensitive_payload", findings, evidence_status)

    runtime_hits = _collect_runtime_action_hits(owner_export)
    if runtime_hits:
        findings.append(
            _finding(
                "RV-09",
                "critical",
                "reject_runtime_action_request",
                "owner export 夾帶 runtime action request；收件驗證只允許脫敏證據，不授權 active response、restart、host write 或掃描。",
                runtime_hits[:12],
            )
        )
        return _validation_result(contract, "reject_runtime_action_request", findings, evidence_status)

    missing_owner_fields = [field for field in required_owner_fields if not _present(owner_export.get(field))]
    if missing_owner_fields:
        findings.append(
            _finding(
                "RV-01",
                "blocker",
                "request_missing_fields",
                "owner export envelope 欄位不足，需要補齊後再驗證。",
                missing_owner_fields,
            )
        )

    count_issue = _validate_counts(owner_export)
    if count_issue:
        findings.append(
            _finding(
                "RV-02",
                "blocker",
                "request_counts_arithmetic_fix",
                count_issue,
                ["agent_total", "agent_active", "agent_disconnected", "agent_never_connected"],
            )
        )

    alias_issue = _validate_aliases(owner_export.get("registry_export_scope_aliases"), expected_aliases)
    if alias_issue:
        findings.append(
            _finding(
                "RV-03",
                "blocker",
                "request_alias_scope_parity_fix",
                alias_issue,
                ["registry_export_scope_aliases"],
            )
        )

    matrix_issue_paths = _validate_per_host_matrix(
        owner_export.get("per_host_registry_matrix"),
        expected_aliases,
        per_host_required_fields,
    )
    if matrix_issue_paths:
        findings.append(
            _finding(
                "RV-04",
                "blocker",
                "request_per_host_matrix_supplement",
                "逐主機矩陣未完整覆蓋 6 個公開別名或缺少必要欄位。",
                matrix_issue_paths[:30],
            )
        )

    dashboard_missing = [field for field in sorted(_DASHBOARD_REQUIRED_FIELDS) if not _present(owner_export.get(field))]
    if dashboard_missing:
        findings.append(
            _finding(
                "RV-05",
                "blocker",
                "request_dashboard_api_repair_postcheck",
                "Dashboard API connection / version / index pattern / root cause / repair postcheck 必須分欄。",
                dashboard_missing,
            )
        )

    readonly_missing = [field for field in sorted(_READONLY_CREDENTIAL_REQUIRED_FIELDS) if not _present(owner_export.get(field))]
    if readonly_missing:
        findings.append(
            _finding(
                "RV-06",
                "blocker",
                "request_readonly_credential_metadata",
                "唯讀 credential metadata 與 manager health ref 必須可追溯，且不得含 secret value。",
                readonly_missing,
            )
        )

    accountability_missing = [field for field in sorted(_ACCOUNTABILITY_REQUIRED_FIELDS) if not _present(owner_export.get(field))]
    if accountability_missing:
        findings.append(
            _finding(
                "RV-08",
                "blocker",
                "request_owner_accountability_supplement",
                "followup owner、rollback owner 與 postcheck plan 必須可讀。",
                accountability_missing,
            )
        )

    _mark_evidence_status(evidence_status, owner_export)
    outcome = _first_blocking_lane(findings) or "accepted_for_readonly_posture_only"
    if outcome == "accepted_for_readonly_posture_only":
        evidence_status = [
            {**slot, "received": True, "accepted": True, "quarantined": False}
            for slot in evidence_status
        ]
        findings.append(
            _finding(
                "RV-10",
                "info",
                "waiting_post_enable_iwooos_readback",
                "owner export 已通過 no-persist reviewer validation；下一步仍只能進 post-enable IwoooS readback，不開 runtime gate。",
                ["post_enable_iwooos_readback"],
            )
        )

    return _validation_result(contract, outcome, findings, evidence_status)


def validate_iwooos_wazuh_manager_registry_acceptance_evidence(
    acceptance_evidence: dict[str, Any],
    security_dir: Path | None = None,
) -> dict[str, Any]:
    """Validate one redacted manager-registry acceptance packet without changing runtime truth."""
    contract = load_latest_iwooos_wazuh_manager_registry_reviewer_validation(security_dir)
    snapshot = _load_snapshot(security_dir or _DEFAULT_SECURITY_DIR)
    expected_aliases = set(_strings(snapshot.get("expected_scope_aliases")))

    findings: list[dict[str, Any]] = []
    if not isinstance(acceptance_evidence, dict):
        findings.append(
            _finding(
                "MRA-01",
                "blocker",
                "request_manager_registry_acceptance_evidence_supplement",
                "manager registry acceptance evidence 必須是 JSON object。",
                [],
            )
        )
        return _acceptance_validation_result(contract, "request_manager_registry_acceptance_evidence_supplement", findings)

    sensitive_hits = _collect_sensitive_hits(acceptance_evidence)
    if sensitive_hits:
        findings.append(
            _finding(
                "MRA-04",
                "critical",
                "quarantine_sensitive_payload",
                "acceptance evidence 含禁止內容或疑似未脫敏內容，已進隔離分流；回應不回傳原始值。",
                [hit["path"] for hit in sensitive_hits[:12]],
                {"categories": sorted({hit["category"] for hit in sensitive_hits})},
            )
        )
        return _acceptance_validation_result(contract, "quarantine_sensitive_payload", findings)

    runtime_hits = _collect_runtime_action_hits(acceptance_evidence)
    if runtime_hits:
        findings.append(
            _finding(
                "MRA-05",
                "critical",
                "reject_runtime_action_request",
                "acceptance evidence 夾帶 runtime action request；此 validator 只允許脫敏 acceptance refs，不授權 active response、restart、host write 或掃描。",
                runtime_hits[:12],
            )
        )
        return _acceptance_validation_result(contract, "reject_runtime_action_request", findings)

    missing_fields = [
        field for field in sorted(_ACCEPTANCE_EVIDENCE_REQUIRED_FIELDS) if not _present(acceptance_evidence.get(field))
    ]
    if missing_fields:
        findings.append(
            _finding(
                "MRA-01",
                "blocker",
                "request_manager_registry_acceptance_evidence_supplement",
                "manager registry acceptance evidence 欄位不足，需要補齊後再驗證。",
                missing_fields,
            )
        )

    count_issue = _validate_acceptance_counts(acceptance_evidence)
    if count_issue:
        findings.append(
            _finding(
                "MRA-02",
                "blocker",
                "request_manager_registry_acceptance_counts_fix",
                count_issue,
                [
                    "accepted_agent_total",
                    "accepted_agent_active",
                    "accepted_agent_disconnected",
                    "accepted_agent_never_connected",
                ],
            )
        )

    alias_issue = _validate_aliases(acceptance_evidence.get("accepted_scope_aliases"), expected_aliases)
    if alias_issue:
        findings.append(
            _finding(
                "MRA-03",
                "blocker",
                "request_manager_registry_acceptance_scope_parity_fix",
                alias_issue,
                ["accepted_scope_aliases"],
            )
        )

    decision = acceptance_evidence.get("acceptance_decision")
    if decision != "accept_manager_registry_evidence_for_review":
        findings.append(
            _finding(
                "MRA-06",
                "blocker",
                "request_manager_registry_acceptance_decision_fix",
                "acceptance_decision 必須明確為 accept_manager_registry_evidence_for_review；不可宣稱已更新 manager registry accepted 總帳。",
                ["acceptance_decision"],
            )
        )

    outcome = _first_blocking_lane(findings) or "accepted_for_manager_registry_acceptance_review_only"
    if outcome == "accepted_for_manager_registry_acceptance_review_only":
        findings.append(
            _finding(
                "MRA-07",
                "info",
                "manager_registry_acceptance_evidence_review_ready",
                "acceptance evidence 已通過 no-persist review readiness；仍不更新全域 manager_registry_accepted_count，不開 runtime gate。",
                ["manager_registry_acceptance_evidence_refs"],
            )
        )
    return _acceptance_validation_result(contract, outcome, findings)


def _validation_result(
    contract: dict[str, Any],
    outcome_lane: str,
    findings: list[dict[str, Any]],
    evidence_status: list[dict[str, Any]],
) -> dict[str, Any]:
    accepted = outcome_lane == "accepted_for_readonly_posture_only"
    quarantined = outcome_lane == "quarantine_sensitive_payload"
    rejected_runtime = outcome_lane == "reject_runtime_action_request"
    return {
        "schema_version": "iwooos_wazuh_manager_registry_owner_export_validation_result_v1",
        "contract_schema_version": contract["schema_version"],
        "status": outcome_lane,
        "mode": "no_persist_validation_no_runtime_no_secret_collection",
        "outcome_lane": outcome_lane,
        "accepted_for_readonly_posture_only": accepted,
        "reviewer_validation_passed": accepted,
        "quarantined": quarantined,
        "runtime_action_rejected": rejected_runtime,
        "summary": {
            "owner_registry_export_received_count": 1,
            "owner_registry_export_accepted_count": 1 if accepted else 0,
            "reviewer_validation_passed_count": 1 if accepted else 0,
            "reviewer_validation_quarantined_count": 1 if quarantined else 0,
            "manager_registry_accepted_count": 0,
            "post_enable_readback_passed_count": 0,
            "runtime_gate_count": 0,
            "host_write_authorized_count": 0,
            "active_response_authorized_count": 0,
            "secret_value_collection_allowed_count": 0,
            "finding_count": len(findings),
        },
        "validation_findings": findings,
        "evidence_slots": evidence_status,
        "boundary_markers": [
            "wazuh_manager_registry_owner_export_validation_received_count=1",
            f"wazuh_manager_registry_owner_export_validation_accepted_count={1 if accepted else 0}",
            f"wazuh_manager_registry_owner_export_validation_quarantined_count={1 if quarantined else 0}",
            "wazuh_manager_registry_owner_export_validation_manager_registry_accepted_count=0",
            "wazuh_manager_registry_owner_export_validation_runtime_gate_count=0",
            "wazuh_manager_registry_owner_export_validation_no_persist=true",
            "wazuh_api_live_query_authorized=false",
            "wazuh_active_response_authorized=false",
            "host_write_authorized=false",
            "secret_value_collection_allowed=false",
            "not_authorization=true",
        ],
        "boundaries": {
            "payload_persisted": False,
            "wazuh_api_live_query_authorized": False,
            "wazuh_agent_reenroll_authorized": False,
            "wazuh_agent_restart_authorized": False,
            "wazuh_manager_restart_authorized": False,
            "wazuh_active_response_authorized": False,
            "host_write_authorized": False,
            "secret_value_collection_allowed": False,
            "raw_wazuh_payload_storage_allowed": False,
            "kali_active_scan_authorized": False,
            "runtime_execution_authorized": False,
            "manager_registry_accepted_updated": False,
            "not_authorization": True,
        },
        "next_gate": "post_enable_iwooos_readback" if accepted else "owner_export_fix_and_resubmit",
    }


def _acceptance_validation_result(
    contract: dict[str, Any],
    outcome_lane: str,
    findings: list[dict[str, Any]],
) -> dict[str, Any]:
    accepted = outcome_lane == "accepted_for_manager_registry_acceptance_review_only"
    quarantined = outcome_lane == "quarantine_sensitive_payload"
    rejected_runtime = outcome_lane == "reject_runtime_action_request"
    supplement_required = not accepted and not quarantined and not rejected_runtime
    return {
        "schema_version": "iwooos_wazuh_manager_registry_acceptance_evidence_validation_result_v1",
        "contract_schema_version": contract["schema_version"],
        "status": outcome_lane,
        "mode": "no_persist_acceptance_evidence_review_no_runtime_no_secret_collection",
        "outcome_lane": outcome_lane,
        "accepted_for_manager_registry_acceptance_review_only": accepted,
        "quarantined": quarantined,
        "runtime_action_rejected": rejected_runtime,
        "summary": {
            "manager_registry_acceptance_evidence_received_count": 1,
            "manager_registry_acceptance_evidence_review_ready_count": 1 if accepted else 0,
            "manager_registry_acceptance_evidence_supplement_required_count": 1 if supplement_required else 0,
            "manager_registry_acceptance_evidence_quarantined_count": 1 if quarantined else 0,
            "manager_registry_acceptance_runtime_action_rejected_count": 1 if rejected_runtime else 0,
            "manager_registry_accepted_count": 0,
            "runtime_gate_count": 0,
            "host_write_authorized_count": 0,
            "active_response_authorized_count": 0,
            "secret_value_collection_allowed_count": 0,
            "finding_count": len(findings),
        },
        "validation_findings": findings,
        "boundary_markers": [
            "wazuh_manager_registry_acceptance_validation_received_count=1",
            f"wazuh_manager_registry_acceptance_validation_review_ready_count={1 if accepted else 0}",
            f"wazuh_manager_registry_acceptance_validation_quarantined_count={1 if quarantined else 0}",
            "wazuh_manager_registry_acceptance_validation_manager_registry_accepted_count=0",
            "wazuh_manager_registry_acceptance_validation_runtime_gate_count=0",
            "wazuh_manager_registry_acceptance_validation_no_persist=true",
            "wazuh_api_live_query_authorized=false",
            "wazuh_active_response_authorized=false",
            "host_write_authorized=false",
            "secret_value_collection_allowed=false",
            "not_authorization=true",
        ],
        "boundaries": {
            "payload_persisted": False,
            "wazuh_api_live_query_authorized": False,
            "wazuh_agent_reenroll_authorized": False,
            "wazuh_agent_restart_authorized": False,
            "wazuh_manager_restart_authorized": False,
            "wazuh_active_response_authorized": False,
            "host_write_authorized": False,
            "secret_value_collection_allowed": False,
            "raw_wazuh_payload_storage_allowed": False,
            "kali_active_scan_authorized": False,
            "runtime_execution_authorized": False,
            "manager_registry_accepted_updated": False,
            "not_authorization": True,
        },
        "next_gate": "commit_manager_registry_accepted_readback_evidence" if accepted else "manager_registry_acceptance_fix_and_resubmit",
    }


def _finding(
    check_id: str,
    severity: str,
    lane: str,
    message: str,
    field_paths: list[str],
    extra: dict[str, Any] | None = None,
) -> dict[str, Any]:
    payload: dict[str, Any] = {
        "check_id": check_id,
        "severity": severity,
        "lane": lane,
        "message": message,
        "field_paths": field_paths,
    }
    if extra:
        payload.update(extra)
    return payload


def _initial_evidence_status(snapshot: dict[str, Any]) -> list[dict[str, Any]]:
    slots = _evidence_slots(snapshot.get("evidence_slots"))
    return [{**slot, "received": False, "accepted": False, "quarantined": False} for slot in slots]


def _mark_evidence_status(evidence_status: list[dict[str, Any]], owner_export: dict[str, Any]) -> None:
    for slot in evidence_status:
        required_fields = slot.get("required_fields", [])
        slot["received"] = bool(required_fields) and all(_present(owner_export.get(field)) for field in required_fields)
        slot["accepted"] = False
        slot["quarantined"] = False


def _present(value: Any) -> bool:
    if value is None:
        return False
    if isinstance(value, str):
        return bool(value.strip())
    if isinstance(value, (list, dict, tuple, set)):
        return bool(value)
    return True


def _int_or_none(value: Any) -> int | None:
    if isinstance(value, bool):
        return None
    if isinstance(value, int):
        return value
    if isinstance(value, str) and value.strip().isdigit():
        return int(value.strip())
    return None


def _validate_counts(owner_export: dict[str, Any]) -> str | None:
    fields = ["agent_total", "agent_active", "agent_disconnected", "agent_never_connected"]
    counts = {field: _int_or_none(owner_export.get(field)) for field in fields}
    if any(value is None for value in counts.values()):
        return "agent count 欄位必須是非負整數。"
    if any(value is not None and value < 0 for value in counts.values()):
        return "agent count 欄位不得為負數。"
    total = counts["agent_total"]
    active = counts["agent_active"]
    disconnected = counts["agent_disconnected"]
    never_connected = counts["agent_never_connected"]
    if total is None or active is None or disconnected is None or never_connected is None:
        return "agent count 欄位必須是非負整數。"
    if total < active + disconnected + never_connected:
        return "agent_total 不得小於 active + disconnected + never_connected。"
    return None


def _validate_acceptance_counts(acceptance_evidence: dict[str, Any]) -> str | None:
    fields = [
        "accepted_agent_total",
        "accepted_agent_active",
        "accepted_agent_disconnected",
        "accepted_agent_never_connected",
    ]
    counts = {field: _int_or_none(acceptance_evidence.get(field)) for field in fields}
    if any(value is None for value in counts.values()):
        return "accepted agent count 欄位必須是非負整數。"
    if any(value is not None and value < 0 for value in counts.values()):
        return "accepted agent count 欄位不得為負數。"
    total = counts["accepted_agent_total"]
    active = counts["accepted_agent_active"]
    disconnected = counts["accepted_agent_disconnected"]
    never_connected = counts["accepted_agent_never_connected"]
    if total is None or active is None or disconnected is None or never_connected is None:
        return "accepted agent count 欄位必須是非負整數。"
    if total < active + disconnected + never_connected:
        return "accepted_agent_total 不得小於 active + disconnected + never_connected。"
    return None


def _validate_aliases(value: Any, expected_aliases: set[str]) -> str | None:
    aliases = value if isinstance(value, list) else []
    if not aliases or not all(isinstance(item, str) for item in aliases):
        return "registry_export_scope_aliases 必須是公開別名字串陣列。"
    alias_set = set(aliases)
    if len(aliases) != len(alias_set):
        return "registry_export_scope_aliases 不得重複。"
    if alias_set != expected_aliases:
        missing = sorted(expected_aliases - alias_set)
        extra = sorted(alias_set - expected_aliases)
        return f"registry_export_scope_aliases 必須剛好等於 6 個公開別名；missing={missing} extra={extra}"
    return None


def _validate_per_host_matrix(value: Any, expected_aliases: set[str], required_fields: list[str]) -> list[str]:
    if not isinstance(value, list):
        return ["per_host_registry_matrix"]
    paths: list[str] = []
    seen_aliases: list[str] = []
    for index, item in enumerate(value):
        if not isinstance(item, dict):
            paths.append(f"per_host_registry_matrix[{index}]")
            continue
        alias = item.get("node_alias")
        if not isinstance(alias, str) or alias not in expected_aliases:
            paths.append(f"per_host_registry_matrix[{index}].node_alias")
        else:
            seen_aliases.append(alias)
        for field in required_fields:
            if not _present(item.get(field)):
                paths.append(f"per_host_registry_matrix[{index}].{field}")
    seen_set = set(seen_aliases)
    if len(seen_aliases) != len(seen_set):
        paths.append("per_host_registry_matrix.duplicate_node_alias")
    for missing_alias in sorted(expected_aliases - seen_set):
        paths.append(f"per_host_registry_matrix.missing.{missing_alias}")
    for extra_alias in sorted(seen_set - expected_aliases):
        paths.append(f"per_host_registry_matrix.extra.{extra_alias}")
    return paths


def _collect_sensitive_hits(value: Any, path: str = "$") -> list[dict[str, str]]:
    hits: list[dict[str, str]] = []
    if isinstance(value, dict):
        for key, item in value.items():
            key_text = str(key)
            key_lower = key_text.lower()
            for fragment in _FORBIDDEN_KEY_FRAGMENTS:
                if fragment in key_lower:
                    hits.append({"path": f"{path}.{key_text}", "category": f"forbidden_key:{fragment}"})
            hits.extend(_collect_sensitive_hits(item, f"{path}.{key_text}"))
        return hits
    if isinstance(value, list):
        for index, item in enumerate(value):
            hits.extend(_collect_sensitive_hits(item, f"{path}[{index}]"))
        return hits
    if isinstance(value, str):
        for category, pattern in _SENSITIVE_TEXT_PATTERNS.items():
            if pattern.search(value):
                hits.append({"path": path, "category": category})
    return hits


def _collect_runtime_action_hits(value: Any, path: str = "$") -> list[str]:
    hits: list[str] = []
    if isinstance(value, dict):
        for key, item in value.items():
            key_text = str(key)
            normalized_key = key_text.lower().replace("-", "_").replace(" ", "_")
            if normalized_key in _RUNTIME_ACTION_KEYS and item not in (False, None, "", [], {}):
                hits.append(f"{path}.{key_text}")
            hits.extend(_collect_runtime_action_hits(item, f"{path}.{key_text}"))
        return hits
    if isinstance(value, list):
        for index, item in enumerate(value):
            hits.extend(_collect_runtime_action_hits(item, f"{path}[{index}]"))
        return hits
    if isinstance(value, str):
        normalized = value.lower().replace("-", "_").replace(" ", "_")
        if normalized in _RUNTIME_ACTION_KEYS:
            hits.append(path)
    return hits


def _first_blocking_lane(findings: list[dict[str, Any]]) -> str | None:
    for lane in (
        "quarantine_sensitive_payload",
        "reject_runtime_action_request",
        "request_missing_fields",
        "request_counts_arithmetic_fix",
        "request_alias_scope_parity_fix",
        "request_per_host_matrix_supplement",
        "request_dashboard_api_repair_postcheck",
        "request_readonly_credential_metadata",
        "request_owner_accountability_supplement",
        "request_manager_registry_acceptance_evidence_supplement",
        "request_manager_registry_acceptance_counts_fix",
        "request_manager_registry_acceptance_scope_parity_fix",
        "request_manager_registry_acceptance_decision_fix",
    ):
        if any(item.get("lane") == lane for item in findings):
            return lane
    return None
