"""MCP transit ledger for cross-platform administration.

The operational router makes one rule non-negotiable: platforms are only
administrable when consultation, diagnosis, action, audit, and explanation
move through MCPs Internos with a complete, traceable transit envelope.  This
module turns the Mais Humana MCP contract report into an explicit ledger of
those envelopes.  The ledger is deterministic, secret-aware, and compact
enough for GPT, SQLite, reports, and future UI renderers.
"""

from __future__ import annotations

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

from .human_rulebook import MCP_CONTROL_PLANE_ID, MCP_TRANSIT_FIELDS, TruthState
from .mcp_contract import McpContractCoverage, McpContractKind, McpContractReport, McpContractStatus, stable_hash
from .models import GeneratedFile, as_plain_data, merge_unique, slugify, utc_now
from .redaction import SECRET_PATTERNS, redact_sensitive_text


class TransitOperation(str, Enum):
    """Administrative operation class required by the router."""

    CONSULTATION = "consulta"
    DIAGNOSIS = "diagnostico"
    ACTION = "acao"
    AUDIT = "auditoria"
    EXPLANATION = "explicacao"


class TransitReadiness(str, Enum):
    """Readiness status for one ledger record."""

    READY = "ready"
    PARTIAL = "partial"
    BLOCKED = "blocked"
    EXTERNAL_DECISION = "external_decision"
    FORMAL_EXCEPTION = "formal_exception"


class FindingSeverity(str, Enum):
    """Severity for transit-policy findings."""

    INFO = "info"
    WARNING = "warning"
    BLOCKER = "blocker"


@dataclass(frozen=True, slots=True)
class McpTransitFinding:
    """One validation finding for a transit record or the whole ledger."""

    finding_id: str
    severity: FindingSeverity
    record_id: str
    field: str
    message: str
    next_action: str

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


@dataclass(frozen=True, slots=True)
class McpTransitRecord:
    """One auditable transit envelope derived from an MCP contract."""

    record_id: str
    contract_id: str
    platform_id: str
    operation: TransitOperation
    origin: str
    destination: str
    tool: str
    payload: Mapping[str, Any]
    actor: str
    permission: str
    result: Mapping[str, Any]
    traceId: str
    auditId: str
    timestamp: str
    sourcePayloadHash: str
    sourceRecordsHash: str
    evidenceId: str
    truthState: str
    panelReady: bool
    gptExplainable: bool
    sameSource: bool
    status: TransitReadiness
    blockers: tuple[str, ...]
    routeClass: str = "mcp_control_plane"

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

    @property
    def envelope(self) -> dict[str, Any]:
        """Return only the mandatory MCP transit envelope."""

        return {
            "origin": self.origin,
            "destination": self.destination,
            "tool": self.tool,
            "payload": self.payload,
            "actor": self.actor,
            "permission": self.permission,
            "result": self.result,
            "traceId": self.traceId,
            "auditId": self.auditId,
            "timestamp": self.timestamp,
        }

    @property
    def is_ready(self) -> bool:
        return self.status in {TransitReadiness.READY, TransitReadiness.FORMAL_EXCEPTION}


@dataclass(frozen=True, slots=True)
class McpTransitLedger:
    """Full ledger of MCP transit records emitted by Mais Humana."""

    ledger_id: str
    generated_at: str
    provider_id: str
    control_plane_id: str
    required_fields: tuple[str, ...]
    records: tuple[McpTransitRecord, ...]
    findings: tuple[McpTransitFinding, ...]
    summary: tuple[str, ...]

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

    @property
    def records_count(self) -> int:
        return len(self.records)

    @property
    def ready_count(self) -> int:
        return sum(1 for item in self.records if item.is_ready)

    @property
    def blocked_count(self) -> int:
        return sum(1 for item in self.records if item.status == TransitReadiness.BLOCKED)

    @property
    def external_decision_count(self) -> int:
        return sum(1 for item in self.records if item.status == TransitReadiness.EXTERNAL_DECISION)

    @property
    def finding_blocker_count(self) -> int:
        return sum(1 for item in self.findings if item.severity == FindingSeverity.BLOCKER)

    @property
    def passed(self) -> bool:
        return self.finding_blocker_count == 0


def _operation_for_kind(kind: McpContractKind) -> TransitOperation:
    if kind == McpContractKind.PROVIDER_TOOL:
        return TransitOperation.EXPLANATION
    if kind == McpContractKind.UI_SCREEN:
        return TransitOperation.CONSULTATION
    if kind == McpContractKind.REPORT_MODEL:
        return TransitOperation.CONSULTATION
    if kind == McpContractKind.TRANSIT_POLICY:
        return TransitOperation.AUDIT
    if kind == McpContractKind.REDACTION_POLICY:
        return TransitOperation.AUDIT
    if kind == McpContractKind.DOCS_EXCEPTION:
        return TransitOperation.DIAGNOSIS
    if kind == McpContractKind.CANONICAL_RENAME:
        return TransitOperation.ACTION
    return TransitOperation.DIAGNOSIS


def _readiness_for_contract(item: McpContractCoverage) -> TransitReadiness:
    if item.status == McpContractStatus.READY:
        return TransitReadiness.READY
    if item.status == McpContractStatus.FORMAL_EXCEPTION:
        return TransitReadiness.FORMAL_EXCEPTION
    if item.status == McpContractStatus.EXTERNAL_DECISION:
        return TransitReadiness.EXTERNAL_DECISION
    if item.status == McpContractStatus.BLOCKED:
        return TransitReadiness.BLOCKED
    return TransitReadiness.PARTIAL


def _payload_for_contract(item: McpContractCoverage, operation: TransitOperation) -> dict[str, Any]:
    return {
        "contractId": item.contract_id,
        "platformId": item.platform_id,
        "profileId": item.profile_id,
        "operation": operation.value,
        "sourceEndpoint": item.source_endpoint,
        "sourceToolId": item.source_tool_id,
        "sourcePayloadHash": item.source_payload_hash,
        "sourceRecordsHash": item.source_records_hash,
        "truthState": item.truth_state.value,
        "panelReady": item.panel_ready,
        "gptExplainable": item.gpt_explainable,
        "sameSource": item.same_source,
        "humanNextAction": item.next_action,
    }


def _result_for_contract(item: McpContractCoverage, status: TransitReadiness) -> dict[str, Any]:
    return {
        "ok": status in {TransitReadiness.READY, TransitReadiness.FORMAL_EXCEPTION},
        "status": status.value,
        "contractStatus": item.status.value,
        "blockers": list(item.blockers),
        "credentialPolicy": "credentialRef/tokenRef/secretRef only; raw bearer/cfat values are forbidden",
        "evidenceId": f"evidence-{item.source_records_hash[:24]}",
    }


def build_transit_record(
    item: McpContractCoverage,
    *,
    provider_id: str,
    control_plane_id: str = MCP_CONTROL_PLANE_ID,
    actor: str = "mais_humana.mcp_transit_ledger",
    permission: str = "mcp.admin.readonly",
) -> McpTransitRecord:
    """Build one deterministic transit record from one contract coverage row."""

    operation = _operation_for_kind(item.kind)
    status = _readiness_for_contract(item)
    payload = _payload_for_contract(item, operation)
    result = _result_for_contract(item, status)
    seed = stable_hash(
        {
            "contractId": item.contract_id,
            "platformId": item.platform_id,
            "operation": operation.value,
            "payload": payload,
            "result": result,
            "actor": actor,
            "permission": permission,
        }
    )
    record_id = f"mcp-transit-{slugify(item.contract_id)}-{seed[:12]}"
    trace_id = f"trace-{seed[:24]}"
    audit_id = f"audit-{stable_hash({'trace': trace_id, 'record': record_id})[:24]}"
    return McpTransitRecord(
        record_id=record_id,
        contract_id=item.contract_id,
        platform_id=item.platform_id,
        operation=operation,
        origin=item.platform_id,
        destination=control_plane_id,
        tool=item.tool_id,
        payload=payload,
        actor=actor,
        permission=permission,
        result=result,
        traceId=trace_id,
        auditId=audit_id,
        timestamp=utc_now(),
        sourcePayloadHash=item.source_payload_hash,
        sourceRecordsHash=item.source_records_hash,
        evidenceId=f"evidence-{item.source_records_hash[:24]}",
        truthState=item.truth_state.value,
        panelReady=item.panel_ready,
        gptExplainable=item.gpt_explainable,
        sameSource=item.same_source,
        status=status,
        blockers=tuple(item.blockers),
    )


def _finding(
    record: McpTransitRecord,
    field: str,
    message: str,
    next_action: str,
    *,
    severity: FindingSeverity = FindingSeverity.BLOCKER,
) -> McpTransitFinding:
    seed = stable_hash({"record": record.record_id, "field": field, "message": message})
    return McpTransitFinding(
        finding_id=f"transit-finding-{seed[:12]}",
        severity=severity,
        record_id=record.record_id,
        field=field,
        message=message,
        next_action=next_action,
    )


def _contains_secret(value: object) -> bool:
    text = str(as_plain_data(value))
    return any(pattern.search(text) for _, pattern in SECRET_PATTERNS)


def _is_empty_required_value(value: object) -> bool:
    if value is None or value == "":
        return True
    if isinstance(value, (tuple, list, dict, set)) and not value:
        return True
    return False


def validate_transit_record(record: McpTransitRecord, *, control_plane_id: str = MCP_CONTROL_PLANE_ID) -> tuple[McpTransitFinding, ...]:
    """Validate one transit record against the router's mandatory envelope."""

    findings: list[McpTransitFinding] = []
    envelope = record.envelope
    for field in MCP_TRANSIT_FIELDS:
        if field not in envelope:
            findings.append(
                _finding(
                    record,
                    field,
                    "campo obrigatorio do envelope MCP ausente",
                    "corrigir gerador de ledger para emitir todos os campos de transito",
                )
            )
        elif _is_empty_required_value(envelope[field]):
            findings.append(
                _finding(
                    record,
                    field,
                    "campo obrigatorio do envelope MCP vazio",
                    "preencher campo antes de considerar plataforma administravel",
                )
            )
    if record.destination != control_plane_id:
        findings.append(
            _finding(
                record,
                "destination",
                "destino nao aponta para MCPs Internos",
                "rotear administracao pelo control-plane MCP antes de expor a capacidade",
            )
        )
    if record.origin == record.destination:
        findings.append(
            _finding(
                record,
                "origin",
                "origem e destino iguais impedem rastrear transito entre plataforma e MCP",
                "separar plataforma de origem e control-plane de destino",
                severity=FindingSeverity.WARNING,
            )
        )
    if _contains_secret(record.payload) or _contains_secret(record.result):
        findings.append(
            _finding(
                record,
                "payload",
                "payload ou resultado contem valor com formato de segredo",
                "substituir por credentialRef, tokenRef ou secretRef e regenerar artefatos",
            )
        )
    if record.status == TransitReadiness.READY and not record.sameSource:
        findings.append(
            _finding(
                record,
                "sameSource",
                "registro pronto sem prova sameSource",
                "baixar prontidao para parcial ou anexar sourcePayloadHash/sourceRecordsHash equivalentes",
                severity=FindingSeverity.WARNING,
            )
        )
    return tuple(findings)


def build_mcp_transit_ledger(report: McpContractReport) -> McpTransitLedger:
    """Build and validate the full MCP transit ledger for a contract report."""

    records = tuple(
        build_transit_record(
            item,
            provider_id=report.provider_id,
            control_plane_id=report.control_plane_id,
        )
        for item in report.coverage
    )
    findings = tuple(finding for record in records for finding in validate_transit_record(record, control_plane_id=report.control_plane_id))
    by_operation = {
        operation.value: sum(1 for record in records if record.operation == operation)
        for operation in TransitOperation
    }
    summary = (
        f"Registros de transito MCP: {len(records)}",
        f"Registros prontos: {sum(1 for item in records if item.is_ready)}",
        f"Registros bloqueados: {sum(1 for item in records if item.status == TransitReadiness.BLOCKED)}",
        f"Decisoes externas: {sum(1 for item in records if item.status == TransitReadiness.EXTERNAL_DECISION)}",
        f"Findings bloqueantes: {sum(1 for item in findings if item.severity == FindingSeverity.BLOCKER)}",
        "Operacoes: " + ", ".join(f"{key}={value}" for key, value in sorted(by_operation.items())),
    )
    ledger_id = f"mcp-transit-ledger-{stable_hash([record.record_id for record in records])[:16]}"
    return McpTransitLedger(
        ledger_id=ledger_id,
        generated_at=utc_now(),
        provider_id=report.provider_id,
        control_plane_id=report.control_plane_id,
        required_fields=MCP_TRANSIT_FIELDS,
        records=records,
        findings=findings,
        summary=summary,
    )


def mcp_transit_ledger_compact_json(ledger: McpTransitLedger, *, limit: int = 80) -> dict[str, Any]:
    records = sorted(ledger.records, key=lambda item: (item.status.value, item.platform_id, item.operation.value, item.record_id))
    return {
        "ledgerId": ledger.ledger_id,
        "generatedAt": ledger.generated_at,
        "providerId": ledger.provider_id,
        "controlPlaneId": ledger.control_plane_id,
        "recordsCount": ledger.records_count,
        "readyCount": ledger.ready_count,
        "blockedCount": ledger.blocked_count,
        "externalDecisionCount": ledger.external_decision_count,
        "findingBlockerCount": ledger.finding_blocker_count,
        "passed": ledger.passed,
        "summary": list(ledger.summary),
        "records": [
            {
                "recordId": item.record_id,
                "contractId": item.contract_id,
                "platformId": item.platform_id,
                "operation": item.operation.value,
                "origin": item.origin,
                "destination": item.destination,
                "tool": item.tool,
                "status": item.status.value,
                "truthState": item.truthState,
                "sameSource": item.sameSource,
                "traceId": item.traceId,
                "auditId": item.auditId,
                "evidenceId": item.evidenceId,
                "sourcePayloadHash": item.sourcePayloadHash,
                "sourceRecordsHash": item.sourceRecordsHash,
            }
            for item in records[:limit]
        ],
        "findings": [item.to_dict() for item in ledger.findings[:limit]],
    }


def mcp_transit_rows(ledger: McpTransitLedger) -> list[list[str]]:
    rows = [
        [
            "record_id",
            "contract_id",
            "platform_id",
            "operation",
            "origin",
            "destination",
            "tool",
            "permission",
            "status",
            "truth_state",
            "same_source",
            "source_payload_hash",
            "source_records_hash",
            "trace_id",
            "audit_id",
            "evidence_id",
            "blockers",
        ]
    ]
    for item in sorted(ledger.records, key=lambda record: (record.platform_id, record.operation.value, record.contract_id)):
        rows.append(
            [
                item.record_id,
                item.contract_id,
                item.platform_id,
                item.operation.value,
                item.origin,
                item.destination,
                item.tool,
                item.permission,
                item.status.value,
                item.truthState,
                str(item.sameSource),
                item.sourcePayloadHash,
                item.sourceRecordsHash,
                item.traceId,
                item.auditId,
                item.evidenceId,
                "; ".join(item.blockers),
            ]
        )
    return rows


def rows_to_csv(rows: Sequence[Sequence[str]]) -> str:
    buffer = io.StringIO()
    writer = csv.writer(buffer, lineterminator="\n")
    writer.writerows(rows)
    return buffer.getvalue()


def mcp_transit_csv(ledger: McpTransitLedger) -> str:
    return rows_to_csv(mcp_transit_rows(ledger))


def mcp_transit_markdown(ledger: McpTransitLedger) -> str:
    lines = [
        "# MCP Transit Ledger Mais Humana",
        "",
        f"- ledger_id: `{ledger.ledger_id}`",
        f"- generated_at: `{ledger.generated_at}`",
        f"- provider_id: `{ledger.provider_id}`",
        f"- control_plane: `{ledger.control_plane_id}`",
        f"- registros: `{ledger.records_count}`",
        f"- prontos: `{ledger.ready_count}`",
        f"- bloqueados: `{ledger.blocked_count}`",
        f"- decisoes_externas: `{ledger.external_decision_count}`",
        f"- findings_bloqueantes: `{ledger.finding_blocker_count}`",
        f"- passed: `{ledger.passed}`",
        "",
        "## Campos obrigatorios",
        "",
    ]
    lines.extend(f"- `{field}`" for field in ledger.required_fields)
    lines.extend(["", "## Sumario", ""])
    lines.extend(f"- {item}" for item in ledger.summary)
    lines.extend(["", "## Registros por plataforma", ""])
    grouped: dict[str, list[McpTransitRecord]] = {}
    for record in ledger.records:
        grouped.setdefault(record.platform_id, []).append(record)
    for platform_id in sorted(grouped):
        records = grouped[platform_id]
        ready = sum(1 for item in records if item.is_ready)
        blocked = sum(1 for item in records if item.status == TransitReadiness.BLOCKED)
        external = sum(1 for item in records if item.status == TransitReadiness.EXTERNAL_DECISION)
        lines.append(f"### {platform_id}")
        lines.append("")
        lines.append(f"- registros: `{len(records)}`")
        lines.append(f"- prontos: `{ready}`")
        lines.append(f"- bloqueados: `{blocked}`")
        lines.append(f"- decisoes_externas: `{external}`")
        for record in sorted(records, key=lambda item: (item.status.value, item.operation.value, item.contract_id))[:8]:
            lines.append(
                f"- `{record.operation.value}` `{record.status.value}` "
                f"`{record.tool}` trace `{record.traceId}` audit `{record.auditId}`"
            )
        lines.append("")
    lines.extend(["## Findings", ""])
    if not ledger.findings:
        lines.append("- Nenhum finding bloqueante ou aviso.")
    else:
        for finding in ledger.findings[:80]:
            lines.append(
                f"- `{finding.severity.value}` `{finding.record_id}` `{finding.field}`: "
                f"{redact_sensitive_text(finding.message)} Proxima acao: {finding.next_action}"
            )
        if len(ledger.findings) > 80:
            lines.append(f"- ... {len(ledger.findings) - 80} findings adicionais no JSON/CSV.")
    lines.append("")
    lines.append("## Regra operacional")
    lines.append("")
    lines.append("- Toda consulta, diagnostico, acao, auditoria e explicacao deve passar pelo MCPs Internos.")
    lines.append("- UI e GPT devem consumir a mesma fonte e os mesmos hashes, sem fonte paralela.")
    lines.append("- Segredos permanecem como referencias opacas; bearer e CFAT brutos nao sao persistidos.")
    return "\n".join(lines).strip() + "\n"


def mcp_transit_artifact_records(project_root: Path) -> tuple[GeneratedFile, ...]:
    records: list[GeneratedFile] = []
    for rel, description, function, file_type in (
        ("dados/mcp-transit-ledger.json", "Ledger completo de transito MCP entre plataformas e MCPs Internos.", "mcp transit ledger", "json"),
        ("dados/mcp-transit-ledger-compacto.json", "Ledger compacto de transito MCP para GPT/UI.", "mcp transit ledger compacto", "json"),
        ("matrizes/mcp-transit-ledger.csv", "Matriz tabular do ledger de transito MCP.", "matriz mcp transit", "csv"),
        ("ecossistema/MCP-TRANSIT-LEDGER.md", "Relatorio do ledger de transito MCP.", "mcp transit ledger", "markdown"),
    ):
        records.append(
            GeneratedFile(
                path=str(project_root / rel),
                description=description,
                function=function,
                file_type=file_type,
                changed_by="mais_humana.mcp_transit_ledger",
                change_summary="Criado ou atualizado ledger de transito MCP rastreavel.",
                relation_to_order="015-ROTEADOR-PERMANENTE-DE-ORDEM_DE_SERVICO",
            )
        )
    return tuple(records)
