"""Acceptance catalog for MCP-only administration routes.

The control contracts already define the administrative routes that must pass
through ``tudo-para-ia-mcps-internos-plataform``.  This module turns the
generated contracts into a compact, executable acceptance layer: the gateway,
GPT, UI renderer, central reports, and service-order closeout can all read the
same case list without traversing the large contract object graph.
"""

from __future__ import annotations

import csv
import io
import json
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Any, Iterable, Mapping, Sequence

from .mcp_contract import MCP_CONTROL_PLANE_ID, PROVIDER_ID, stable_hash
from .models import GeneratedFile, as_plain_data, merge_unique, utc_now
from .redaction import redact_sensitive_text


try:
    from .generated_mcp_admin_route_acceptance import (
        ADMIN_ROUTE_ACCEPTANCE_CASES as GENERATED_ADMIN_ROUTE_ACCEPTANCE_CASES,
        SOURCE_HASH as GENERATED_SOURCE_HASH,
    )
except ImportError:  # pragma: no cover - exercised before the generator runs.
    GENERATED_ADMIN_ROUTE_ACCEPTANCE_CASES: tuple[Mapping[str, Any], ...] = ()
    GENERATED_SOURCE_HASH = ""


class AdminRouteAcceptanceStatus(str, Enum):
    """Acceptance status for one generated route case."""

    READY = "ready"
    PARTIAL = "partial"
    BLOCKED = "blocked"


class AdminRouteOperation(str, Enum):
    """Canonical administration operation names."""

    CONSULTA = "consulta"
    DIAGNOSTICO = "diagnostico"
    ACAO = "acao"
    AUDITORIA = "auditoria"
    EXPLICACAO = "explicacao"


MANDATORY_TRANSIT_FIELDS = (
    "origin",
    "destination",
    "tool",
    "payload",
    "actor",
    "permission",
    "result",
    "traceId",
    "auditId",
    "timestamp",
)

MANDATORY_PAYLOAD_FIELDS = (
    "adminRouteId",
    "adminRouteKind",
    "sourceEndpoint",
    "sourceToolId",
    "sourcePayloadHash",
    "sourceRecordsHash",
    "truthState",
    "panelReady",
    "gptExplainable",
    "humanNextAction",
    "permissionScope",
    "mcpOnlyAdministration",
)


@dataclass(frozen=True, slots=True)
class AdminRouteAcceptanceCase:
    """One MCP-only administration route acceptance case."""

    route_id: str
    platform_id: str
    profile_id: str
    operation: str
    tool_id: str
    source_tool_id: str
    permission_scope: str
    source_endpoint: str
    source_payload_hash: str
    source_records_hash: str
    evidence_id: str
    truth_state: str
    panel_ready: bool
    gpt_explainable: bool
    same_source: bool
    approval_required: bool
    dry_run_supported: bool
    required_transit_fields: tuple[str, ...]
    required_payload_fields: tuple[str, ...]
    validation_steps: tuple[str, ...]
    redaction_requirements: tuple[str, ...]
    pending_if_missing: str
    order_ids: tuple[str, ...]
    policy_tags: tuple[str, ...]
    maturity_level: int
    status: AdminRouteAcceptanceStatus
    blocker_reasons: tuple[str, ...]

    @property
    def route_key(self) -> str:
        return f"{self.platform_id}/{self.profile_id}/{self.operation}"

    @property
    def ready(self) -> bool:
        return self.status == AdminRouteAcceptanceStatus.READY

    def to_dict(self) -> dict[str, Any]:
        return as_plain_data(self)


@dataclass(frozen=True, slots=True)
class AdminRouteAcceptanceSummary:
    """Aggregated acceptance result for all generated administration routes."""

    generated_at: str
    provider_id: str
    control_plane_id: str
    source_hash: str
    total_cases: int
    ready_cases: int
    partial_cases: int
    blocked_cases: int
    platforms_count: int
    profiles_count: int
    operations: Mapping[str, int]
    platforms: Mapping[str, int]
    blockers: tuple[str, ...]
    evidence_id: str

    @property
    def status(self) -> AdminRouteAcceptanceStatus:
        if self.blocked_cases:
            return AdminRouteAcceptanceStatus.BLOCKED
        if self.partial_cases:
            return AdminRouteAcceptanceStatus.PARTIAL
        return AdminRouteAcceptanceStatus.READY if self.total_cases else AdminRouteAcceptanceStatus.BLOCKED

    @property
    def ready_ratio(self) -> float:
        if not self.total_cases:
            return 0.0
        return self.ready_cases / self.total_cases

    def to_dict(self) -> dict[str, Any]:
        data = as_plain_data(self)
        data["status"] = self.status.value
        data["readyRatio"] = self.ready_ratio
        return data


@dataclass(frozen=True, slots=True)
class AdminRouteAcceptanceReport:
    """Full acceptance report with a bounded sample and summary."""

    report_id: str
    generated_at: str
    summary: AdminRouteAcceptanceSummary
    returned_cases: tuple[AdminRouteAcceptanceCase, ...]
    sample_limit: int
    filters: Mapping[str, str]
    generated_files: tuple[str, ...] = ()

    @property
    def status(self) -> AdminRouteAcceptanceStatus:
        return self.summary.status

    def to_dict(self) -> dict[str, Any]:
        data = as_plain_data(self)
        data["status"] = self.status.value
        return data


def _tuple(value: object) -> tuple[str, ...]:
    if value is None:
        return ()
    if isinstance(value, str):
        return (value,)
    if isinstance(value, Iterable):
        return tuple(str(item) for item in value)
    return (str(value),)


def _bool(value: object) -> bool:
    if isinstance(value, bool):
        return value
    return str(value).strip().lower() in {"1", "true", "yes", "sim"}


def _status(value: object) -> AdminRouteAcceptanceStatus:
    text = str(value or "").strip()
    for status in AdminRouteAcceptanceStatus:
        if status.value == text:
            return status
    return AdminRouteAcceptanceStatus.BLOCKED


def case_from_mapping(row: Mapping[str, Any]) -> AdminRouteAcceptanceCase:
    """Convert generated plain data into a typed acceptance case."""

    return AdminRouteAcceptanceCase(
        route_id=str(row.get("routeId") or row.get("route_id") or ""),
        platform_id=str(row.get("platformId") or row.get("platform_id") or ""),
        profile_id=str(row.get("profileId") or row.get("profile_id") or ""),
        operation=str(row.get("operation") or ""),
        tool_id=str(row.get("toolId") or row.get("tool_id") or ""),
        source_tool_id=str(row.get("sourceToolId") or row.get("source_tool_id") or ""),
        permission_scope=str(row.get("permissionScope") or row.get("permission_scope") or ""),
        source_endpoint=str(row.get("sourceEndpoint") or row.get("source_endpoint") or ""),
        source_payload_hash=str(row.get("sourcePayloadHash") or row.get("source_payload_hash") or ""),
        source_records_hash=str(row.get("sourceRecordsHash") or row.get("source_records_hash") or ""),
        evidence_id=str(row.get("evidenceId") or row.get("evidence_id") or ""),
        truth_state=str(row.get("truthState") or row.get("truth_state") or ""),
        panel_ready=_bool(row.get("panelReady") or row.get("panel_ready")),
        gpt_explainable=_bool(row.get("gptExplainable") or row.get("gpt_explainable")),
        same_source=_bool(row.get("sameSource") or row.get("same_source")),
        approval_required=_bool(row.get("approvalRequired") or row.get("approval_required")),
        dry_run_supported=_bool(row.get("dryRunSupported") or row.get("dry_run_supported")),
        required_transit_fields=_tuple(row.get("requiredTransitFields") or row.get("required_transit_fields")),
        required_payload_fields=_tuple(row.get("requiredPayloadFields") or row.get("required_payload_fields")),
        validation_steps=_tuple(row.get("validationSteps") or row.get("validation_steps")),
        redaction_requirements=_tuple(row.get("redactionRequirements") or row.get("redaction_requirements")),
        pending_if_missing=str(row.get("pendingIfMissing") or row.get("pending_if_missing") or ""),
        order_ids=_tuple(row.get("orderIds") or row.get("order_ids")),
        policy_tags=_tuple(row.get("policyTags") or row.get("policy_tags")),
        maturity_level=int(row.get("maturityLevel") or row.get("maturity_level") or 0),
        status=_status(row.get("status")),
        blocker_reasons=_tuple(row.get("blockerReasons") or row.get("blocker_reasons")),
    )


def iter_admin_route_acceptance_cases(
    *,
    platform_id: str | None = None,
    profile_id: str | None = None,
    operation: str | None = None,
    status: str | None = None,
    limit: int | None = None,
) -> tuple[AdminRouteAcceptanceCase, ...]:
    """Return generated acceptance cases, optionally filtered."""

    output: list[AdminRouteAcceptanceCase] = []
    for raw in GENERATED_ADMIN_ROUTE_ACCEPTANCE_CASES:
        case = case_from_mapping(raw)
        if platform_id and case.platform_id != platform_id:
            continue
        if profile_id and case.profile_id != profile_id:
            continue
        if operation and case.operation != operation:
            continue
        if status and case.status.value != status:
            continue
        output.append(case)
        if limit is not None and len(output) >= max(0, int(limit)):
            break
    return tuple(output)


def _count_by(cases: Sequence[AdminRouteAcceptanceCase], attr: str) -> dict[str, int]:
    counts: dict[str, int] = {}
    for case in cases:
        value = str(getattr(case, attr))
        counts[value] = counts.get(value, 0) + 1
    return dict(sorted(counts.items()))


def summarize_admin_route_acceptance(cases: Sequence[AdminRouteAcceptanceCase] | None = None) -> AdminRouteAcceptanceSummary:
    """Build a compact readiness summary for MCP-only administration routes."""

    case_set = tuple(cases if cases is not None else iter_admin_route_acceptance_cases())
    blockers = merge_unique(
        f"{case.route_id}:{reason}"
        for case in case_set
        for reason in case.blocker_reasons
        if case.status != AdminRouteAcceptanceStatus.READY
    )
    evidence_id = "evidence-" + stable_hash(
        {
            "sourceHash": GENERATED_SOURCE_HASH,
            "caseCount": len(case_set),
            "ready": sum(1 for case in case_set if case.status == AdminRouteAcceptanceStatus.READY),
            "blockers": blockers,
        }
    )[:24]
    return AdminRouteAcceptanceSummary(
        generated_at=utc_now(),
        provider_id=PROVIDER_ID,
        control_plane_id=MCP_CONTROL_PLANE_ID,
        source_hash=GENERATED_SOURCE_HASH,
        total_cases=len(case_set),
        ready_cases=sum(1 for case in case_set if case.status == AdminRouteAcceptanceStatus.READY),
        partial_cases=sum(1 for case in case_set if case.status == AdminRouteAcceptanceStatus.PARTIAL),
        blocked_cases=sum(1 for case in case_set if case.status == AdminRouteAcceptanceStatus.BLOCKED),
        platforms_count=len({case.platform_id for case in case_set}),
        profiles_count=len({case.profile_id for case in case_set}),
        operations=_count_by(case_set, "operation"),
        platforms=_count_by(case_set, "platform_id"),
        blockers=blockers[:80],
        evidence_id=evidence_id,
    )


def build_admin_route_acceptance_report(
    *,
    platform_id: str | None = None,
    profile_id: str | None = None,
    operation: str | None = None,
    status: str | None = None,
    limit: int = 80,
) -> AdminRouteAcceptanceReport:
    """Build a report with full summary and bounded returned cases."""

    all_filtered = iter_admin_route_acceptance_cases(
        platform_id=platform_id,
        profile_id=profile_id,
        operation=operation,
        status=status,
    )
    returned = all_filtered[: max(0, int(limit))]
    summary = summarize_admin_route_acceptance(all_filtered)
    filter_payload = {
        "platformId": platform_id or "",
        "profileId": profile_id or "",
        "operation": operation or "",
        "status": status or "",
    }
    report_id = "mcp-admin-route-acceptance-" + stable_hash(
        {
            "summaryEvidence": summary.evidence_id,
            "filters": filter_payload,
            "limit": limit,
        }
    )[:16]
    return AdminRouteAcceptanceReport(
        report_id=report_id,
        generated_at=utc_now(),
        summary=summary,
        returned_cases=tuple(returned),
        sample_limit=max(0, int(limit)),
        filters=filter_payload,
    )


def compact_acceptance_payload(report: AdminRouteAcceptanceReport) -> dict[str, Any]:
    """Return a compact payload for GPT/MCP/UI discovery."""

    return {
        "reportId": report.report_id,
        "generatedAt": report.generated_at,
        "providerId": report.summary.provider_id,
        "controlPlaneId": report.summary.control_plane_id,
        "status": report.status.value,
        "sourceHash": report.summary.source_hash,
        "totalCases": report.summary.total_cases,
        "readyCases": report.summary.ready_cases,
        "partialCases": report.summary.partial_cases,
        "blockedCases": report.summary.blocked_cases,
        "readyRatio": report.summary.ready_ratio,
        "operations": dict(report.summary.operations),
        "platformsCount": report.summary.platforms_count,
        "profilesCount": report.summary.profiles_count,
        "evidenceId": report.summary.evidence_id,
        "returnedCases": len(report.returned_cases),
        "cases": [
            {
                "routeId": case.route_id,
                "platformId": case.platform_id,
                "profileId": case.profile_id,
                "operation": case.operation,
                "toolId": case.tool_id,
                "permissionScope": case.permission_scope,
                "sameSource": case.same_source,
                "status": case.status.value,
                "evidenceId": case.evidence_id,
                "sourcePayloadHash": case.source_payload_hash,
                "sourceRecordsHash": case.source_records_hash,
                "humanNextAction": case.pending_if_missing,
            }
            for case in report.returned_cases
        ],
    }


def acceptance_csv(report: AdminRouteAcceptanceReport) -> str:
    """Render acceptance cases as CSV."""

    rows = [
        [
            "route_id",
            "platform_id",
            "profile_id",
            "operation",
            "tool_id",
            "permission_scope",
            "same_source",
            "status",
            "approval_required",
            "dry_run_supported",
            "evidence_id",
            "blockers",
        ]
    ]
    for case in report.returned_cases:
        rows.append(
            [
                case.route_id,
                case.platform_id,
                case.profile_id,
                case.operation,
                case.tool_id,
                case.permission_scope,
                "yes" if case.same_source else "no",
                case.status.value,
                "yes" if case.approval_required else "no",
                "yes" if case.dry_run_supported else "no",
                case.evidence_id,
                "; ".join(case.blocker_reasons),
            ]
        )
    buffer = io.StringIO()
    writer = csv.writer(buffer, lineterminator="\n")
    writer.writerows(rows)
    return buffer.getvalue()


def acceptance_markdown(report: AdminRouteAcceptanceReport) -> str:
    """Render a human-readable route acceptance report."""

    lines = [
        "# MCP Admin Route Acceptance",
        "",
        f"- report_id: `{report.report_id}`",
        f"- generated_at: `{report.generated_at}`",
        f"- provider_id: `{report.summary.provider_id}`",
        f"- control_plane_id: `{report.summary.control_plane_id}`",
        f"- status: `{report.status.value}`",
        f"- source_hash: `{report.summary.source_hash}`",
        f"- total_cases: `{report.summary.total_cases}`",
        f"- ready_cases: `{report.summary.ready_cases}`",
        f"- partial_cases: `{report.summary.partial_cases}`",
        f"- blocked_cases: `{report.summary.blocked_cases}`",
        f"- ready_ratio: `{report.summary.ready_ratio:.4f}`",
        f"- evidence_id: `{report.summary.evidence_id}`",
        "",
        "## Operacoes",
        "",
    ]
    if report.summary.operations:
        lines.extend(f"- `{name}`: `{count}`" for name, count in sorted(report.summary.operations.items()))
    else:
        lines.append("- Nenhuma rota administrativa materializada.")
    lines.extend(["", "## Plataformas", ""])
    if report.summary.platforms:
        lines.extend(f"- `{name}`: `{count}`" for name, count in sorted(report.summary.platforms.items()))
    else:
        lines.append("- Nenhuma plataforma materializada.")
    lines.extend(["", "## Amostra", ""])
    for case in report.returned_cases[:30]:
        lines.extend(
            [
                f"### {case.route_id}",
                "",
                f"- route_key: `{case.route_key}`",
                f"- tool_id: `{case.tool_id}`",
                f"- source_tool_id: `{case.source_tool_id}`",
                f"- permission_scope: `{case.permission_scope}`",
                f"- same_source: `{case.same_source}`",
                f"- status: `{case.status.value}`",
                f"- evidence_id: `{case.evidence_id}`",
                f"- source_payload_hash: `{case.source_payload_hash}`",
                f"- source_records_hash: `{case.source_records_hash}`",
                f"- next_action: {case.pending_if_missing}",
                "",
            ]
        )
    lines.extend(["## Bloqueios", ""])
    if report.summary.blockers:
        lines.extend(f"- `{redact_sensitive_text(item)}`" for item in report.summary.blockers[:80])
    else:
        lines.append("- Nenhum bloqueio de contrato MCP-only nas rotas administrativas geradas.")
    lines.extend(
        [
            "",
            "## Regra operacional",
            "",
            "- Toda consulta, diagnostico, acao, auditoria e explicacao interplataforma deve transitar pelo MCPs Internos.",
            "- Cada caso preserva origin, destination, tool, payload, actor, permission, result, traceId, auditId e timestamp.",
            "- Valores de segredo bruto nao fazem parte deste catalogo; evidencias usam hashes, traceId, auditId e refs opacas.",
        ]
    )
    return "\n".join(lines).strip() + "\n"


def acceptance_artifact_records(project_root: Path, central_platform_folder: Path | None = None) -> tuple[GeneratedFile, ...]:
    """Return semantic records for acceptance artifacts."""

    records = [
        GeneratedFile(
            path=str(project_root / "dados" / "mcp-admin-route-acceptance.json"),
            description="Catalogo estruturado de aceitacao das rotas administrativas MCP-only.",
            function="mcp admin route acceptance report",
            file_type="json",
            changed_by="mais_humana.mcp_admin_route_acceptance",
            change_summary="Materializado catalogo auditavel de rotas administrativas MCP-only.",
            relation_to_order="0037_EXECUTIVA__homologar-rotas-administrativas-mcp-no-gateway",
        ),
        GeneratedFile(
            path=str(project_root / "dados" / "mcp-admin-route-acceptance-compacto.json"),
            description="Resumo compacto das rotas administrativas para GPT/MCP/UI.",
            function="mcp admin route acceptance compact payload",
            file_type="json",
            changed_by="mais_humana.mcp_admin_route_acceptance",
            change_summary="Criado payload compacto com contadores, hashes e amostra de rotas.",
            relation_to_order="0037_EXECUTIVA__homologar-rotas-administrativas-mcp-no-gateway",
        ),
        GeneratedFile(
            path=str(project_root / "matrizes" / "mcp-admin-route-acceptance.csv"),
            description="Matriz tabular de aceitacao das rotas administrativas MCP-only.",
            function="mcp admin route acceptance matrix",
            file_type="csv",
            changed_by="mais_humana.mcp_admin_route_acceptance",
            change_summary="Criada matriz para auditoria de operacao, permissao e same-source.",
            relation_to_order="0037_EXECUTIVA__homologar-rotas-administrativas-mcp-no-gateway",
        ),
        GeneratedFile(
            path=str(project_root / "ecossistema" / "MCP-ADMIN-ROUTE-ACCEPTANCE.md"),
            description="Relatorio humano de aceitacao das rotas administrativas MCP-only.",
            function="mcp admin route acceptance human report",
            file_type="markdown",
            changed_by="mais_humana.mcp_admin_route_acceptance",
            change_summary="Criado relatorio de homologacao local das rotas administrativas.",
            relation_to_order="0037_EXECUTIVA__homologar-rotas-administrativas-mcp-no-gateway",
        ),
    ]
    if central_platform_folder is not None:
        records.extend(
            [
                GeneratedFile(
                    path=str(central_platform_folder / "reports" / "EXECUTADO__mcp-admin-route-acceptance.md"),
                    description="Copia central do relatorio de aceitacao das rotas administrativas.",
                    function="mcp admin route acceptance central report",
                    file_type="markdown",
                    changed_by="mais_humana.mcp_admin_route_acceptance",
                    change_summary="Registrada homologacao local das rotas administrativas na central.",
                    relation_to_order="0037_EXECUTIVA__homologar-rotas-administrativas-mcp-no-gateway",
                ),
                GeneratedFile(
                    path=str(central_platform_folder / "indexes" / "mcp-admin-route-acceptance-index.md"),
                    description="Indice central do catalogo de rotas administrativas MCP-only.",
                    function="mcp admin route acceptance central index",
                    file_type="markdown",
                    changed_by="mais_humana.mcp_admin_route_acceptance",
                    change_summary="Indexado status, contadores e evidencia da aceitacao MCP-only.",
                    relation_to_order="0037_EXECUTIVA__homologar-rotas-administrativas-mcp-no-gateway",
                ),
            ]
        )
    return tuple(records)


def _write_text(path: Path, text: str) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(text, encoding="utf-8")


def write_admin_route_acceptance_artifacts(
    report: AdminRouteAcceptanceReport,
    project_root: Path,
    *,
    central_platform_folder: Path | None = None,
) -> tuple[GeneratedFile, ...]:
    """Write project and optional central route acceptance artifacts."""

    full_json = json.dumps(report.to_dict(), ensure_ascii=False, indent=2, sort_keys=True)
    compact_json = json.dumps(compact_acceptance_payload(report), ensure_ascii=False, indent=2, sort_keys=True)
    markdown = acceptance_markdown(report)
    targets: list[tuple[Path, str]] = [
        (project_root / "dados" / "mcp-admin-route-acceptance.json", full_json),
        (project_root / "dados" / "mcp-admin-route-acceptance-compacto.json", compact_json),
        (project_root / "matrizes" / "mcp-admin-route-acceptance.csv", acceptance_csv(report)),
        (project_root / "ecossistema" / "MCP-ADMIN-ROUTE-ACCEPTANCE.md", markdown),
    ]
    records = list(acceptance_artifact_records(project_root, central_platform_folder))
    central_failures: list[dict[str, str]] = []
    if central_platform_folder is not None:
        targets.extend(
            [
                (central_platform_folder / "reports" / "EXECUTADO__mcp-admin-route-acceptance.md", markdown),
                (central_platform_folder / "indexes" / "mcp-admin-route-acceptance-index.md", markdown),
            ]
        )
    for path, content in targets:
        try:
            _write_text(path, content)
        except OSError as exc:
            if central_platform_folder is not None and central_platform_folder in path.parents:
                central_failures.append({"path": str(path), "error": f"{type(exc).__name__}: {exc}"})
                continue
            raise
    if central_failures:
        status_path = project_root / "dados" / "mcp-admin-route-acceptance-central-write-status.json"
        _write_text(
            status_path,
            json.dumps(
                {
                    "generatedAt": utc_now(),
                    "ok": False,
                    "centralPlatformFolder": str(central_platform_folder),
                    "failures": central_failures,
                },
                ensure_ascii=False,
                indent=2,
                sort_keys=True,
            ),
        )
        records.append(
            GeneratedFile(
                path=str(status_path),
                description="Status de escrita central do catalogo de rotas administrativas.",
                function="mcp admin route acceptance central write status",
                file_type="json",
                changed_by="mais_humana.mcp_admin_route_acceptance",
                change_summary="Registrada falha de escrita central sem abortar artefatos do projeto real.",
                relation_to_order="0040_EXECUTIVA__materializar-escrita-central-e-sql-semantico-sem-permissionerror",
            )
        )
    return tuple(records)


def run_admin_route_acceptance(
    *,
    project_root: Path,
    central_platform_folder: Path | None = None,
    platform_id: str | None = None,
    profile_id: str | None = None,
    operation: str | None = None,
    status: str | None = None,
    limit: int = 120,
) -> tuple[AdminRouteAcceptanceReport, tuple[GeneratedFile, ...]]:
    """Build and write route acceptance artifacts."""

    report = build_admin_route_acceptance_report(
        platform_id=platform_id,
        profile_id=profile_id,
        operation=operation,
        status=status,
        limit=limit,
    )
    records = write_admin_route_acceptance_artifacts(report, project_root, central_platform_folder=central_platform_folder)
    report_with_files = AdminRouteAcceptanceReport(
        report_id=report.report_id,
        generated_at=report.generated_at,
        summary=report.summary,
        returned_cases=report.returned_cases,
        sample_limit=report.sample_limit,
        filters=report.filters,
        generated_files=tuple(record.path for record in records),
    )
    return report_with_files, records
