"""AI Agent log intelligence integration readback.

This service exposes a source-of-truth map for LOG -> KM/RAG/MCP/PlayBook
automation. It only inspects committed source files packaged with the API image;
it does not query live logs, read secrets, write KM, update PlayBook trust,
call MCP tools, trigger workflows, or execute runtime repairs.
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from src.services.snapshot_paths import resolve_repo_root

_SCHEMA_VERSION = "ai_agent_log_intelligence_integration_readback_v1"
_RUNTIME_SAMPLE_SCHEMA_VERSION = "ai_agent_log_intelligence_runtime_sample_readback_v1"
_RUNTIME_SAMPLE_FILE = "ai-agent-log-intelligence-runtime-sample-readback.snapshot.json"
_DEFAULT_REPO_ROOT = resolve_repo_root(Path(__file__))

_LANES: tuple[dict[str, Any], ...] = (
    {
        "lane_id": "structured_service_log_collection",
        "title": "服務日誌與可觀測性來源",
        "source_kind": "logs_metrics_traces",
        "integration_target": "diagnosis_context",
        "required_refs": (
            "apps/api/src/core/logging.py",
            "apps/api/src/core/deep_linking.py",
            "apps/api/src/services/diagnosis_aggregator.py",
        ),
        "required_labels": ("project_id", "service", "environment", "trace_id", "severity"),
    },
    {
        "lane_id": "log_classification_summary",
        "title": "Log 分類、摘要與異常訊號",
        "source_kind": "log_classification",
        "integration_target": "agent_evidence_packet",
        "required_refs": (
            "apps/api/src/services/log_anomaly_detector.py",
            "apps/api/src/services/log_summary_service.py",
            "apps/api/src/services/diagnosis_aggregator.py",
        ),
        "required_labels": ("signal_kind", "severity", "service", "package", "tool"),
    },
    {
        "lane_id": "km_rag_consumption",
        "title": "KM / RAG 證據消費",
        "source_kind": "knowledge_memory",
        "integration_target": "rag_context",
        "required_refs": (
            "apps/api/src/services/knowledge_extractor_service.py",
            "apps/api/src/services/knowledge_service.py",
            "apps/api/src/services/knowledge_rag_service.py",
            "apps/api/src/services/rag_service.py",
            "apps/api/src/services/graph_rag.py",
        ),
        "required_labels": ("project_id", "product", "service", "incident_id", "evidence_ref"),
    },
    {
        "lane_id": "playbook_learning_loop",
        "title": "PlayBook 匹配、RAG 與 trust 學習",
        "source_kind": "playbook_learning",
        "integration_target": "playbook_candidate_and_trust_gate",
        "required_refs": (
            "apps/api/src/services/playbook_rag.py",
            "apps/api/src/services/playbook_match_resolver.py",
            "apps/api/src/services/playbook_embedding_service.py",
            "apps/api/src/services/playbook_evolver.py",
            "apps/api/src/services/ai_agent_matched_playbook_learning_gap.py",
        ),
        "required_labels": ("playbook_id", "incident_id", "service", "risk_tier", "verifier_id"),
    },
    {
        "lane_id": "mcp_tool_audit_context",
        "title": "MCP 工具、稽核上下文與工具註冊",
        "source_kind": "mcp_tools",
        "integration_target": "controlled_tool_context",
        "required_refs": (
            "apps/api/src/plugins/mcp/mcp_bridge.py",
            "apps/api/src/services/mcp_audit_context.py",
            "apps/api/src/services/mcp_audit_service.py",
            "apps/api/src/services/mcp_tool_registry.py",
        ),
        "required_labels": ("mcp_server", "tool", "agent_run_id", "gateway_path", "redaction_state"),
    },
    {
        "lane_id": "agent_decision_runtime_context",
        "title": "AI Agent 決策與結果捕獲",
        "source_kind": "agent_runtime",
        "integration_target": "controlled_decision_runtime",
        "required_refs": (
            "apps/api/src/services/ai_agent_autonomous_runtime_control.py",
            "apps/api/src/services/ai_agent_task_result_audit_trail.py",
            "apps/api/src/services/ai_agent_result_capture_writer_dry_run_readback.py",
            "apps/api/src/services/ai_agent_result_capture_no_write_readback.py",
        ),
        "required_labels": ("agent_run_id", "decision_id", "risk_tier", "source_system", "verifier_id"),
    },
    {
        "lane_id": "post_apply_verifier_feedback",
        "title": "Verifier 與 post-apply feedback",
        "source_kind": "verification_feedback",
        "integration_target": "learning_writeback_candidate",
        "required_refs": (
            "apps/api/src/services/ai_agent_runtime_verifier_evidence_review.py",
            "apps/api/src/services/ai_agent_post_write_verifier_package.py",
            "apps/api/src/services/ai_agent_result_capture_post_release_verifier_rollback_gate.py",
        ),
        "required_labels": ("verifier_id", "rollback_ref", "service", "environment", "decision_id"),
    },
)

_LABEL_TAXONOMY: tuple[dict[str, Any], ...] = (
    {
        "label_group": "ownership",
        "required_fields": ("project_id", "product", "service", "owner_lane"),
        "purpose": "把所有 log / event 綁回專案、產品、服務與 owner lane。",
    },
    {
        "label_group": "runtime_surface",
        "required_fields": ("environment", "host", "package", "tool", "source_system"),
        "purpose": "把網站、服務、套件、工具與主機來源分群。",
    },
    {
        "label_group": "correlation",
        "required_fields": ("trace_id", "incident_id", "agent_run_id", "decision_id"),
        "purpose": "把 log、告警、AI run、AwoooP work item 與 verifier 串成同一條證據鏈。",
    },
    {
        "label_group": "learning",
        "required_fields": ("playbook_id", "verifier_id", "risk_tier", "redaction_state"),
        "purpose": "讓 KM/RAG/PlayBook trust writeback 能判斷是否可學習。",
    },
)


def load_latest_ai_agent_log_intelligence_integration_readback(
    repo_root: Path | None = None,
) -> dict[str, Any]:
    """Build the latest LOG -> AI automation integration readback."""
    root = repo_root or _DEFAULT_REPO_ROOT
    lanes = [_build_lane(root, lane) for lane in _LANES]
    ready_lane_count = sum(1 for lane in lanes if lane["status"] == "source_refs_present")
    missing_lane_count = len(lanes) - ready_lane_count
    runtime_sample = _load_runtime_sample(root)
    runtime_sample_present = runtime_sample is not None
    runtime_samples = runtime_sample.get("samples", []) if runtime_sample else []
    current_p0_blocker_sample_present = _sample_present(
        runtime_samples,
        "p0_110_harbor_runner_control_path_sample",
    )
    controlled_recovery_package_sample_present = _sample_event_present(
        runtime_samples,
        "local_recovery_package_packaged",
    )
    label_field_count = len(
        {field for group in _LABEL_TAXONOMY for field in group["required_fields"]}
    )
    active_blockers = []
    if missing_lane_count:
        active_blockers.append("committed_source_refs_missing")
    if not runtime_sample_present:
        active_blockers.append("runtime_e2e_log_sample_readback_missing")

    return {
        "schema_version": _SCHEMA_VERSION,
        "priority": "P1-LOG-KM-RAG-MCP-PLAYBOOK",
        "scope": "ai_agent_log_intelligence_integration",
        "status": (
            "blocked_missing_committed_source_refs"
            if missing_lane_count
            else (
                "controlled_apply_ready_for_trusted_feedback_receipt"
                if runtime_sample_present
                else "controlled_apply_ready_missing_runtime_e2e_log_sample"
            )
        ),
        "readback": {
            "workplan_id": "P1-LOG-INTELLIGENCE",
            "workplan_title": "所有服務日誌貼標並串接 KM / RAG / MCP / PlayBook / AI Agent",
            "repo_root_ref": str(root),
            "safe_next_step": (
                "run_trusted_feedback_receipt_dry_run_then_enable_km_rag_playbook_verifier"
                if runtime_sample_present
                else "add_runtime_log_sample_verifier_then_write_trusted_KM_RAG_PlayBook_feedback_receipt"
            ),
        },
        "integration_lanes": lanes,
        "label_taxonomy": list(_LABEL_TAXONOMY),
        "runtime_e2e_verifier": runtime_sample
        or {
            "schema_version": _RUNTIME_SAMPLE_SCHEMA_VERSION,
            "status": "missing_runtime_sample_readback",
            "expected_ref": f"docs/operations/{_RUNTIME_SAMPLE_FILE}",
        },
        "rollups": {
            "lane_count": len(lanes),
            "ready_lane_count": ready_lane_count,
            "missing_lane_count": missing_lane_count,
            "source_ref_count": sum(len(lane["evidence_refs"]) for lane in lanes),
            "missing_source_ref_count": sum(len(lane["missing_refs"]) for lane in lanes),
            "readiness_percent": _percent(ready_lane_count / max(len(lanes), 1) * 100),
            "label_group_count": len(_LABEL_TAXONOMY),
            "required_label_field_count": label_field_count,
            "runtime_e2e_log_sample_readback_present": runtime_sample_present,
            "runtime_sample_count": len(runtime_sample.get("samples", []))
            if runtime_sample
            else 0,
            "current_p0_blocker_runtime_sample_present": (
                current_p0_blocker_sample_present
            ),
            "controlled_recovery_package_sample_present": (
                controlled_recovery_package_sample_present
            ),
            "km_rag_feedback_receipt_ready": _feedback_ready(
                runtime_sample,
                "km_rag_feedback_receipt_ready",
            ),
            "playbook_feedback_receipt_ready": _feedback_ready(
                runtime_sample,
                "playbook_feedback_receipt_ready",
            ),
            "mcp_audit_feedback_receipt_ready": _feedback_ready(
                runtime_sample,
                "mcp_audit_feedback_receipt_ready",
            ),
            "km_rag_playbook_trust_writeback_authorized": False,
            "mcp_tool_execution_authorized": False,
        },
        "active_blockers": active_blockers,
        "operation_boundaries": {
            "read_only_api_allowed": True,
            "live_log_query_performed": False,
            "km_write_performed": False,
            "rag_index_write_performed": False,
            "playbook_trust_write_performed": False,
            "mcp_tool_call_performed": False,
            "runtime_repair_performed": False,
            "workflow_trigger_performed": False,
            "secret_value_collection_allowed": False,
            "raw_session_or_sqlite_read_allowed": False,
            "github_api_used": False,
        },
    }


def _load_runtime_sample(root: Path) -> dict[str, Any] | None:
    path = root / "docs" / "operations" / _RUNTIME_SAMPLE_FILE
    if not path.exists():
        return None
    with path.open(encoding="utf-8") as handle:
        payload = json.load(handle)
    if not isinstance(payload, dict):
        raise ValueError(f"{path}: expected JSON object")
    _require_runtime_sample(payload, str(path))
    return payload


def _require_runtime_sample(payload: dict[str, Any], label: str) -> None:
    if payload.get("schema_version") != _RUNTIME_SAMPLE_SCHEMA_VERSION:
        raise ValueError(f"{label}: unsupported schema_version={payload.get('schema_version')!r}")
    samples = payload.get("samples")
    if not isinstance(samples, list) or not samples:
        raise ValueError(f"{label}: samples must be a non-empty list")
    for sample in samples:
        if not isinstance(sample, dict):
            raise ValueError(f"{label}: sample must be an object")
        for field in ("sample_id", "service", "source_system", "environment", "project_id"):
            if not sample.get(field):
                raise ValueError(f"{label}: sample missing {field}")
        if sample.get("raw_log_payload_persisted") is not False:
            raise ValueError(f"{label}: raw log payload must not be persisted")

    receipt = payload.get("feedback_receipt_readiness") or {}
    required_ready = {
        "km_rag_feedback_receipt_ready",
        "playbook_feedback_receipt_ready",
        "mcp_audit_feedback_receipt_ready",
        "post_apply_verifier_feedback_receipt_ready",
        "trusted_writeback_still_requires_dry_run",
    }
    missing_ready = sorted(field for field in required_ready if receipt.get(field) is not True)
    if missing_ready:
        raise ValueError(f"{label}: feedback receipt readiness missing: {missing_ready}")

    boundaries = payload.get("operation_boundaries") or {}
    if boundaries.get("runtime_log_sample_read_performed") is not True:
        raise ValueError(f"{label}: runtime log sample readback must be true")
    forbidden_true = sorted(
        field
        for field in (
            "raw_log_payload_persisted",
            "secret_value_collection_allowed",
            "km_write_performed",
            "rag_index_write_performed",
            "playbook_trust_write_performed",
            "mcp_tool_call_performed",
            "runtime_repair_performed",
            "workflow_trigger_performed",
            "github_api_used",
        )
        if boundaries.get(field) is not False
    )
    if forbidden_true:
        raise ValueError(f"{label}: forbidden operation boundary true: {forbidden_true}")


def _feedback_ready(runtime_sample: dict[str, Any] | None, field: str) -> bool:
    if not runtime_sample:
        return False
    return (runtime_sample.get("feedback_receipt_readiness") or {}).get(field) is True


def _sample_present(samples: Any, sample_id: str) -> bool:
    return any(
        isinstance(sample, dict) and sample.get("sample_id") == sample_id
        for sample in samples
    )


def _sample_event_present(samples: Any, event: str) -> bool:
    return any(
        isinstance(sample, dict)
        and event in {str(item) for item in sample.get("observed_events", [])}
        for sample in samples
    )


def _build_lane(root: Path, lane: dict[str, Any]) -> dict[str, Any]:
    evidence_refs = [str(ref) for ref in lane["required_refs"]]
    missing_refs = [ref for ref in evidence_refs if not _ref_exists(root, ref)]
    return {
        "lane_id": lane["lane_id"],
        "title": lane["title"],
        "source_kind": lane["source_kind"],
        "integration_target": lane["integration_target"],
        "status": "source_refs_present" if not missing_refs else "missing_source_refs",
        "required_labels": list(lane["required_labels"]),
        "evidence_refs": evidence_refs,
        "missing_refs": missing_refs,
        "controlled_apply_next_step": "attach_runtime_sample_and_post_apply_verifier",
        "runtime_write_enabled": False,
        "secret_value_collection_allowed": False,
    }


def _ref_exists(root: Path, ref: str) -> bool:
    if (root / ref).exists():
        return True
    if ref.startswith("apps/api/"):
        return (root / ref.removeprefix("apps/api/")).exists()
    return False


def _percent(value: Any) -> int:
    return max(0, min(100, round(float(value or 0))))
