"""Evidence graph derived from governance, workflows, registry, and orders."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Iterable, Sequence

from .exit_order_compiler import CompiledOrderSet
from .governance_models import EcosystemGovernancePortfolio
from .human_readiness_registry import ReadinessRegistry
from .mcp_contract import McpContractReport
from .models import as_plain_data, merge_unique, slugify
from .workflow_registry import WorkflowPortfolio


@dataclass(slots=True)
class EvidenceNode:
    node_id: str
    label: str
    node_type: str
    status: str
    weight: int

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


@dataclass(slots=True)
class EvidenceEdge:
    source: str
    target: str
    relation: str
    weight: int
    reason: str

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


@dataclass(slots=True)
class EvidenceGraph:
    nodes: tuple[EvidenceNode, ...]
    edges: tuple[EvidenceEdge, ...]
    summary: tuple[str, ...]

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


def node_id(*parts: str) -> str:
    return slugify(".".join(parts))


def add_node(nodes: dict[str, EvidenceNode], node: EvidenceNode) -> None:
    if node.node_id not in nodes:
        nodes[node.node_id] = node


def add_edge(edges: list[EvidenceEdge], source: str, target: str, relation: str, weight: int, reason: str) -> None:
    edges.append(EvidenceEdge(source=source, target=target, relation=relation, weight=weight, reason=reason))


def platform_nodes(portfolio: EcosystemGovernancePortfolio, nodes: dict[str, EvidenceNode]) -> None:
    for card in portfolio.cards:
        add_node(
            nodes,
            EvidenceNode(
                node_id=node_id("platform", card.platform_id),
                label=card.platform_id,
                node_type="platform",
                status=card.status_label,
                weight=card.governance_score,
            ),
        )


def governance_nodes_edges(portfolio: EcosystemGovernancePortfolio, nodes: dict[str, EvidenceNode], edges: list[EvidenceEdge]) -> None:
    for card in portfolio.cards:
        platform_node = node_id("platform", card.platform_id)
        for check in card.checks:
            if check.status.value == "not_applicable":
                continue
            check_node = node_id("check", card.platform_id, check.check_id)
            add_node(
                nodes,
                EvidenceNode(
                    node_id=check_node,
                    label=check.title,
                    node_type="check",
                    status=check.status.value,
                    weight=check.score,
                ),
            )
            add_edge(edges, platform_node, check_node, f"has_check:{check.domain.value}", check.score, check.reason)
            for evidence in check.evidence[:4]:
                evidence_node = node_id("evidence", evidence.reference)
                add_node(
                    nodes,
                    EvidenceNode(
                        node_id=evidence_node,
                        label=evidence.reference,
                        node_type="evidence",
                        status=evidence.kind.value,
                        weight=round(evidence.confidence * 100),
                    ),
                )
                add_edge(edges, check_node, evidence_node, "supported_by", round(evidence.confidence * 100), evidence.summary)


def registry_nodes_edges(registry: ReadinessRegistry, nodes: dict[str, EvidenceNode], edges: list[EvidenceEdge]) -> None:
    for entry in registry.entries:
        if entry.status == "pronto":
            continue
        profile_node = node_id("profile", entry.profile_id)
        platform_node = node_id("platform", entry.platform_id)
        entry_node = node_id("readiness", entry.platform_id, entry.profile_id)
        add_node(nodes, EvidenceNode(profile_node, entry.profile_name, "profile", "tracked", entry.human_score))
        add_node(nodes, EvidenceNode(entry_node, f"{entry.platform_id}/{entry.profile_id}", "readiness", entry.status, entry.human_score))
        add_edge(edges, platform_node, entry_node, "serves_profile", entry.human_score, entry.recommended_action)
        add_edge(edges, entry_node, profile_node, "for_profile", entry.human_score, entry.recommended_action)


def workflow_nodes_edges(workflows: WorkflowPortfolio, nodes: dict[str, EvidenceNode], edges: list[EvidenceEdge]) -> None:
    by_workflow = {workflow.workflow_id: workflow for workflow in workflows.workflows}
    for evaluation in workflows.evaluations:
        workflow = by_workflow[evaluation.workflow_id]
        workflow_node = node_id("workflow", workflow.workflow_id)
        add_node(nodes, EvidenceNode(workflow_node, workflow.title, "workflow", evaluation.status, evaluation.score))
        for platform_id in workflow.platforms:
            platform_node = node_id("platform", platform_id)
            add_edge(edges, workflow_node, platform_node, "depends_on_platform", evaluation.score, workflow.purpose)
        for step in workflow.steps:
            step_node = node_id("workflow-step", workflow.workflow_id, step.step_id)
            add_node(nodes, EvidenceNode(step_node, step.title, "workflow_step", "tracked", evaluation.score))
            add_edge(edges, workflow_node, step_node, "has_step", evaluation.score, step.validation)
            add_edge(edges, step_node, node_id("platform", step.owner_platform), "owned_by", evaluation.score, step.human_output)


def order_nodes_edges(compiled: CompiledOrderSet | None, nodes: dict[str, EvidenceNode], edges: list[EvidenceEdge]) -> None:
    if compiled is None:
        return
    for order in compiled.service_orders:
        order_node = node_id("order", order.order_id)
        platform_hint = "ecosystem"
        lowered = f"{order.object_scope} {order.reason}".lower()
        for token in ("business", "compliance", "customer_ops", "docs", "finance", "gettys", "identity", "integracoes", "intelligence", "mcps", "platform_base", "public", "stj", "ui"):
            if token in lowered or token.replace("_", "-") in lowered:
                platform_hint = token
                break
        add_node(nodes, EvidenceNode(order_node, order.title, "service_order", order.status.value, 50))
        add_edge(edges, node_id("platform", platform_hint), order_node, f"creates_{order.order_type.value}", 50, order.reason)


def mcp_contract_nodes_edges(report: McpContractReport | None, nodes: dict[str, EvidenceNode], edges: list[EvidenceEdge]) -> None:
    if report is None:
        return
    provider_node = node_id("mcp-provider", report.provider_id)
    add_node(
        nodes,
        EvidenceNode(
            node_id=provider_node,
            label=report.provider_name,
            node_type="mcp_provider",
            status="published_contract",
            weight=report.average_score,
        ),
    )
    control_plane_node = node_id("platform", "mcps")
    add_edge(edges, control_plane_node, provider_node, "discovers_provider_contract", report.average_score, report.provider_envelope.tool)
    for item in report.coverage:
        if item.kind.value not in {"provider_tool", "ui_screen", "redaction_policy", "docs_exception", "canonical_rename"}:
            continue
        contract_node = node_id("mcp-contract", item.contract_id)
        add_node(
            nodes,
            EvidenceNode(
                node_id=contract_node,
                label=item.contract_id,
                node_type=f"mcp_{item.kind.value}",
                status=item.status.value,
                weight=item.score,
            ),
        )
        add_edge(edges, provider_node, contract_node, "publishes_contract", item.score, item.next_action)
        add_edge(edges, contract_node, node_id("platform", item.platform_id), "governs_platform", item.score, item.truth_state.value)
        if item.same_source:
            add_edge(edges, contract_node, control_plane_node, "same_source_ready", item.score, item.source_payload_hash[:16])


def relation_edges(portfolio: EcosystemGovernancePortfolio, edges: list[EvidenceEdge]) -> None:
    for source, target, relation in portfolio.relation_matrix:
        add_edge(edges, node_id("platform", source), node_id("platform", target), relation, 40, "relacao declarada ou inferida pelo dominio")


def dedupe_edges(edges: Iterable[EvidenceEdge]) -> tuple[EvidenceEdge, ...]:
    seen: set[tuple[str, str, str]] = set()
    output: list[EvidenceEdge] = []
    for edge in edges:
        key = (edge.source, edge.target, edge.relation)
        if key in seen:
            continue
        seen.add(key)
        output.append(edge)
    output.sort(key=lambda item: (item.source, item.relation, item.target))
    return tuple(output)


def build_evidence_graph(
    portfolio: EcosystemGovernancePortfolio,
    registry: ReadinessRegistry,
    workflows: WorkflowPortfolio,
    compiled_orders: CompiledOrderSet | None = None,
    mcp_contract_report: McpContractReport | None = None,
) -> EvidenceGraph:
    nodes: dict[str, EvidenceNode] = {}
    edges: list[EvidenceEdge] = []
    platform_nodes(portfolio, nodes)
    governance_nodes_edges(portfolio, nodes, edges)
    registry_nodes_edges(registry, nodes, edges)
    workflow_nodes_edges(workflows, nodes, edges)
    order_nodes_edges(compiled_orders, nodes, edges)
    mcp_contract_nodes_edges(mcp_contract_report, nodes, edges)
    relation_edges(portfolio, edges)
    final_edges = dedupe_edges(edges)
    summary = (
        f"Nos: {len(nodes)}",
        f"Arestas: {len(final_edges)}",
        f"Plataformas: {sum(1 for node in nodes.values() if node.node_type == 'platform')}",
        f"Checks: {sum(1 for node in nodes.values() if node.node_type == 'check')}",
        f"Workflows: {sum(1 for node in nodes.values() if node.node_type == 'workflow')}",
    )
    return EvidenceGraph(nodes=tuple(sorted(nodes.values(), key=lambda item: item.node_id)), edges=final_edges, summary=summary)


def graph_markdown(graph: EvidenceGraph, limit: int = 160) -> str:
    lines = ["# Grafo de evidencias Mais Humana", ""]
    lines.extend(f"- {item}" for item in graph.summary)
    lines.extend(["", "## Nos principais", ""])
    for node in sorted(graph.nodes, key=lambda item: (-item.weight, item.node_type, item.node_id))[:limit]:
        lines.append(f"- `{node.node_id}` [{node.node_type}] {node.label} status `{node.status}` peso `{node.weight}`")
    lines.extend(["", "## Arestas principais", ""])
    for edge in sorted(graph.edges, key=lambda item: (-item.weight, item.source, item.target))[:limit]:
        lines.append(f"- `{edge.source}` -> `{edge.target}` ({edge.relation}) peso `{edge.weight}` - {edge.reason}")
    return "\n".join(lines).strip() + "\n"


def graph_dot(graph: EvidenceGraph, limit_edges: int = 260) -> str:
    lines = ["digraph MaisHumanaEvidence {", "  rankdir=LR;", "  node [shape=box, style=rounded];"]
    for node in graph.nodes:
        label = node.label.replace('"', "'")[:80]
        color = "red" if node.status in {"blocked", "bloqueado", "critico"} else "orange" if node.status in {"attention", "atencao"} else "gray"
        lines.append(f'  "{node.node_id}" [label="{label}", color="{color}"];')
    for edge in graph.edges[:limit_edges]:
        label = edge.relation.replace('"', "'")[:60]
        lines.append(f'  "{edge.source}" -> "{edge.target}" [label="{label}"];')
    lines.append("}")
    return "\n".join(lines) + "\n"


def graph_rows(graph: EvidenceGraph) -> list[list[str]]:
    rows = [["kind", "source", "target", "relation", "status", "weight", "label"]]
    for node in graph.nodes:
        rows.append(["node", node.node_id, "", node.node_type, node.status, str(node.weight), node.label])
    for edge in graph.edges:
        rows.append(["edge", edge.source, edge.target, edge.relation, "", str(edge.weight), edge.reason])
    return rows


def graph_focus(graph: EvidenceGraph, token: str) -> tuple[str, ...]:
    lowered = token.lower()
    lines: list[str] = []
    for node in graph.nodes:
        if lowered in node.node_id.lower() or lowered in node.label.lower() or lowered in node.status.lower():
            lines.append(f"node {node.node_id}: {node.label} ({node.status})")
    for edge in graph.edges:
        if lowered in edge.source.lower() or lowered in edge.target.lower() or lowered in edge.relation.lower() or lowered in edge.reason.lower():
            lines.append(f"edge {edge.source}->{edge.target}: {edge.relation}")
    return merge_unique(lines)[:80]
