"""Reconciliation logic for repository mesh reports.

Inventory is only the first half of the permanent order.  The harder part is
deciding what to do when mirrors disagree without accidentally giving priority
to an old checkout just because a synchronizer ran later.  This module keeps
that decision explicit and reviewable.

The reconciler accepts a :class:`~mais_humana.repository_mesh.MeshReport` and
builds:

* a latest-valid-state analysis per repository;
* safe fast-forward eligibility;
* local rename transaction plans;
* credential and environment follow-up receipts;
* service-order suggestions tied to real blockers.

It does not execute the plan.  Execution belongs to a controlled runner after
the receipt is reviewed.
"""

from __future__ import annotations

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

from .models import GeneratedFile, OrderStatus, OrderType, ServiceOrder, as_plain_data, incrementing_id, merge_unique, utc_now
from .repository_mesh import (
    MeshAction,
    MeshActionKind,
    MeshEnvironmentKind,
    MeshErrorKind,
    MeshPresence,
    MeshReport,
    MeshRepositorySummary,
    MeshRiskLevel,
    MirrorObservation,
    RepositoryTarget,
    rows_to_csv,
    stable_digest,
)
from .repository_mesh_naming import NamingDecision, NamingStatus, decide_naming


class ReconciliationStatus(str, Enum):
    """Result of reconciliation for one repository."""

    ALIGNED = "aligned"
    FAST_FORWARD_READY = "fast_forward_ready"
    NOMINAL_RENAME_READY = "nominal_rename_ready"
    CREDENTIAL_BLOCKED = "credential_blocked"
    DIRTY_BLOCKED = "dirty_blocked"
    ENVIRONMENT_BLOCKED = "environment_blocked"
    MANUAL_DECISION_REQUIRED = "manual_decision_required"
    MISSING_MATERIALIZATION = "missing_materialization"
    REMOTE_MISMATCH = "remote_mismatch"
    UNKNOWN = "unknown"


class StatePrecedence(str, Enum):
    """Why one state can or cannot be considered latest valid state."""

    SINGLE_HASH = "single_hash"
    LOCAL_AHEAD = "local_ahead"
    REMOTE_AHEAD = "remote_ahead"
    DIVERGENT = "divergent"
    DIRTY_TREE = "dirty_tree"
    MISSING_HASH = "missing_hash"
    ENVIRONMENT_MISSING = "environment_missing"
    MANUAL_ONLY = "manual_only"


class ReceiptSeverity(str, Enum):
    """Severity for reconciliation receipts."""

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


@dataclass(frozen=True, slots=True)
class CandidateState:
    """One commit/state candidate observed in the mesh."""

    target_name: str
    environment_id: str
    environment_kind: MeshEnvironmentKind
    path: str
    branch: str
    head: str
    remote: str
    cleanliness: str
    ahead: int
    behind: int
    upstream: str
    status_preview: tuple[str, ...] = ()
    observed_at: str = ""

    @property
    def short_head(self) -> str:
        return self.head[:12]

    @property
    def can_be_source(self) -> bool:
        return bool(self.head) and self.cleanliness == "clean" and self.ahead >= 0 and self.behind >= 0

    @property
    def has_local_only_commits(self) -> bool:
        return self.ahead > 0 and self.behind == 0

    @property
    def has_remote_only_commits(self) -> bool:
        return self.behind > 0 and self.ahead == 0

    @property
    def divergent(self) -> bool:
        return self.ahead > 0 and self.behind > 0

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


@dataclass(frozen=True, slots=True)
class LatestStateDecision:
    """Decision about the latest valid state for one repository."""

    target_name: str
    precedence: StatePrecedence
    selected_head: str | None
    selected_environment: str | None
    selected_path: str | None
    candidates: tuple[CandidateState, ...]
    rejected_heads: tuple[str, ...]
    reason: str
    confidence: float
    blockers: tuple[str, ...] = ()

    @property
    def has_blocker(self) -> bool:
        return bool(self.blockers)

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


@dataclass(frozen=True, slots=True)
class PlannedOperation:
    """A controlled operation produced by reconciliation."""

    operation_id: str
    target_name: str
    status: ReconciliationStatus
    severity: ReceiptSeverity
    title: str
    reason: str
    commands: tuple[str, ...] = ()
    preconditions: tuple[str, ...] = ()
    rollback: tuple[str, ...] = ()
    evidence: tuple[str, ...] = ()
    automatic: bool = False
    destructive: bool = False

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


@dataclass(frozen=True, slots=True)
class ReconciliationReceipt:
    """Auditable reconciliation receipt for one repository."""

    receipt_id: str
    target_name: str
    expected_local_name: str
    gitea_repo: str
    status: ReconciliationStatus
    risk: MeshRiskLevel
    latest_state: LatestStateDecision
    naming_decisions: tuple[NamingDecision, ...]
    operations: tuple[PlannedOperation, ...]
    pending_items: tuple[str, ...]
    safe_to_auto_sync: bool
    generated_at: str = field(default_factory=utc_now)

    @property
    def blockers(self) -> tuple[str, ...]:
        return tuple(item.reason for item in self.operations if item.severity == ReceiptSeverity.BLOCKER)

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


@dataclass(frozen=True, slots=True)
class ReconciliationPlan:
    """Full reconciliation plan for a mesh report."""

    plan_id: str
    report_id: str
    generated_at: str
    receipts: tuple[ReconciliationReceipt, ...]
    summary: tuple[str, ...]

    @property
    def blocker_count(self) -> int:
        return sum(1 for receipt in self.receipts if receipt.blockers)

    @property
    def auto_sync_ready_count(self) -> int:
        return sum(1 for receipt in self.receipts if receipt.safe_to_auto_sync)

    @property
    def rename_ready_count(self) -> int:
        return sum(1 for receipt in self.receipts if receipt.status == ReconciliationStatus.NOMINAL_RENAME_READY)

    @property
    def credential_blocked_count(self) -> int:
        return sum(1 for receipt in self.receipts if receipt.status == ReconciliationStatus.CREDENTIAL_BLOCKED)

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


def candidate_from_observation(observation: MirrorObservation) -> CandidateState | None:
    """Create a candidate state from a Git observation."""

    state = observation.git_state
    if state is None or not state.head:
        return None
    ahead = state.ahead_behind.ahead if state.ahead_behind.ahead is not None else -1
    behind = state.ahead_behind.behind if state.ahead_behind.behind is not None else -1
    return CandidateState(
        target_name=observation.target_name,
        environment_id=observation.environment_id,
        environment_kind=observation.environment_kind,
        path=observation.matched_path or observation.expected_path,
        branch=state.branch or "",
        head=state.head,
        remote=state.remote_origin or "",
        cleanliness=state.cleanliness.value,
        ahead=ahead,
        behind=behind,
        upstream=state.upstream or "",
        status_preview=state.dirty_preview(),
        observed_at=observation.observed_at,
    )


def candidate_states(summary: MeshRepositorySummary) -> tuple[CandidateState, ...]:
    """Return all commit candidates for a repository summary."""

    return tuple(
        candidate
        for candidate in (candidate_from_observation(obs) for obs in summary.observations)
        if candidate is not None
    )


def dirty_observations(summary: MeshRepositorySummary) -> tuple[MirrorObservation, ...]:
    return tuple(
        obs
        for obs in summary.observations
        if obs.git_state is not None and obs.git_state.cleanliness.value == "dirty"
    )


def credential_observations(summary: MeshRepositorySummary) -> tuple[MirrorObservation, ...]:
    return tuple(
        obs
        for obs in summary.observations
        if obs.git_state is not None
        and obs.git_state.fetch_error_kind in {MeshErrorKind.CREDENTIALS_MISSING, MeshErrorKind.AUTHENTICATION}
    )


def environment_blocked_observations(summary: MeshRepositorySummary) -> tuple[MirrorObservation, ...]:
    return tuple(obs for obs in summary.observations if obs.presence == MeshPresence.UNREACHABLE_ENVIRONMENT)


def missing_observations(summary: MeshRepositorySummary) -> tuple[MirrorObservation, ...]:
    return tuple(obs for obs in summary.observations if obs.presence == MeshPresence.MISSING)


def nominal_actions(summary: MeshRepositorySummary) -> tuple[MeshAction, ...]:
    return tuple(
        item
        for item in summary.actions
        if item.kind in {MeshActionKind.RENAME_LOCAL_FOLDER, MeshActionKind.CREATE_ALIAS_RECORD}
    )


def remote_mismatch_actions(summary: MeshRepositorySummary) -> tuple[MeshAction, ...]:
    return tuple(item for item in summary.actions if item.kind == MeshActionKind.FIX_REMOTE_URL)


def naming_decisions(summary: MeshRepositorySummary) -> tuple[NamingDecision, ...]:
    """Evaluate expected, alias, canonical, platform, and plataform names per reachable root."""

    decisions: list[NamingDecision] = []
    seen_roots: set[str] = set()
    for observation in summary.observations:
        if observation.presence == MeshPresence.UNREACHABLE_ENVIRONMENT:
            continue
        root = str(Path(observation.expected_path).parent)
        if root in seen_roots:
            continue
        seen_roots.add(root)
        decisions.append(decide_naming(Path(root), summary.target))
    return tuple(decisions)


def select_latest_state(summary: MeshRepositorySummary) -> LatestStateDecision:
    """Select latest valid state or explain why manual decision is required."""

    candidates = candidate_states(summary)
    blockers: list[str] = []
    if not candidates:
        return LatestStateDecision(
            target_name=summary.target.declared_name,
            precedence=StatePrecedence.MISSING_HASH,
            selected_head=None,
            selected_environment=None,
            selected_path=None,
            candidates=(),
            rejected_heads=(),
            reason="nenhum HEAD foi confirmado nos ambientes acessiveis",
            confidence=0.0,
            blockers=("sem hash HEAD confirmado",),
        )
    dirty = tuple(item for item in candidates if item.cleanliness == "dirty")
    if dirty:
        blockers.extend(f"{item.environment_id} possui working tree sujo" for item in dirty)
        return LatestStateDecision(
            target_name=summary.target.declared_name,
            precedence=StatePrecedence.DIRTY_TREE,
            selected_head=None,
            selected_environment=None,
            selected_path=None,
            candidates=candidates,
            rejected_heads=merge_unique(item.head for item in candidates),
            reason="estado sujo impede declarar commit como fonte unica de verdade",
            confidence=0.25,
            blockers=tuple(blockers),
        )
    divergent = tuple(item for item in candidates if item.divergent)
    if divergent:
        blockers.extend(f"{item.environment_id} esta ahead={item.ahead} behind={item.behind}" for item in divergent)
        return LatestStateDecision(
            target_name=summary.target.declared_name,
            precedence=StatePrecedence.DIVERGENT,
            selected_head=None,
            selected_environment=None,
            selected_path=None,
            candidates=candidates,
            rejected_heads=merge_unique(item.head for item in candidates),
            reason="divergencia ahead/behind exige decisao humana de precedencia",
            confidence=0.2,
            blockers=tuple(blockers),
        )
    unique_heads = merge_unique(item.head for item in candidates if item.head)
    if len(unique_heads) == 1:
        selected = candidates[0]
        return LatestStateDecision(
            target_name=summary.target.declared_name,
            precedence=StatePrecedence.SINGLE_HASH,
            selected_head=unique_heads[0],
            selected_environment=selected.environment_id,
            selected_path=selected.path,
            candidates=candidates,
            rejected_heads=(),
            reason="todos os ambientes com HEAD confirmado apontam para o mesmo commit",
            confidence=0.95 if len(candidates) > 1 else 0.75,
        )
    local_ahead = tuple(item for item in candidates if item.has_local_only_commits)
    if len(local_ahead) == 1:
        selected = local_ahead[0]
        return LatestStateDecision(
            target_name=summary.target.declared_name,
            precedence=StatePrecedence.LOCAL_AHEAD,
            selected_head=selected.head,
            selected_environment=selected.environment_id,
            selected_path=selected.path,
            candidates=candidates,
            rejected_heads=tuple(head for head in unique_heads if head != selected.head),
            reason="um ambiente possui commits locais a frente e nao ha behind registrado",
            confidence=0.72,
            blockers=("push exige credencial e validacao de remoto antes de convergir",),
        )
    remote_ahead = tuple(item for item in candidates if item.has_remote_only_commits)
    if remote_ahead and len(unique_heads) > 1:
        selected = sorted(remote_ahead, key=lambda item: (item.behind, item.environment_id), reverse=True)[0]
        return LatestStateDecision(
            target_name=summary.target.declared_name,
            precedence=StatePrecedence.REMOTE_AHEAD,
            selected_head=selected.head,
            selected_environment=selected.environment_id,
            selected_path=selected.path,
            candidates=candidates,
            rejected_heads=tuple(head for head in unique_heads if head != selected.head),
            reason="ha indicio de commits remotos nao materializados localmente",
            confidence=0.6,
            blockers=("aplicar somente fast-forward comprovado em working tree limpo",),
        )
    return LatestStateDecision(
        target_name=summary.target.declared_name,
        precedence=StatePrecedence.MANUAL_ONLY,
        selected_head=None,
        selected_environment=None,
        selected_path=None,
        candidates=candidates,
        rejected_heads=unique_heads,
        reason="hashes distintos sem regra objetiva suficiente para eleger fonte mais recente",
        confidence=0.1,
        blockers=("comparacao manual de commits necessaria",),
    )


def operation_id(target: RepositoryTarget, title: str, evidence: Iterable[str] = ()) -> str:
    return f"mesh-op-{stable_digest({'target': target.declared_name, 'title': title, 'evidence': list(evidence)}, 12)}"


def planned_operation(
    target: RepositoryTarget,
    status: ReconciliationStatus,
    severity: ReceiptSeverity,
    title: str,
    reason: str,
    *,
    commands: Iterable[str] = (),
    preconditions: Iterable[str] = (),
    rollback: Iterable[str] = (),
    evidence: Iterable[str] = (),
    automatic: bool = False,
    destructive: bool = False,
) -> PlannedOperation:
    evidence_tuple = tuple(evidence)
    return PlannedOperation(
        operation_id=operation_id(target, title, evidence_tuple),
        target_name=target.declared_name,
        status=status,
        severity=severity,
        title=title,
        reason=reason,
        commands=tuple(commands),
        preconditions=tuple(preconditions),
        rollback=tuple(rollback),
        evidence=evidence_tuple,
        automatic=automatic,
        destructive=destructive,
    )


def operations_from_summary(
    summary: MeshRepositorySummary,
    latest: LatestStateDecision,
    naming: Sequence[NamingDecision] = (),
) -> tuple[PlannedOperation, ...]:
    """Translate low-level actions into high-level reconciliation operations."""

    target = summary.target
    operations: list[PlannedOperation] = []
    for obs in dirty_observations(summary):
        preview = obs.git_state.dirty_preview() if obs.git_state else ()
        operations.append(
            planned_operation(
                target,
                ReconciliationStatus.DIRTY_BLOCKED,
                ReceiptSeverity.BLOCKER,
                "Bloquear sincronizacao destrutiva por working tree sujo",
                "alteracoes locais nao commitadas podem ser a versao valida mais recente",
                preconditions=("registrar diff", "commit ou stash consciente", "decidir precedencia antes de pull/merge"),
                evidence=(obs.environment_id, obs.matched_path or "", *preview),
                destructive=False,
            )
        )
    for obs in credential_observations(summary):
        fetch = obs.git_state.fetch_result.compact() if obs.git_state and obs.git_state.fetch_result else ""
        operations.append(
            planned_operation(
                target,
                ReconciliationStatus.CREDENTIAL_BLOCKED,
                ReceiptSeverity.BLOCKER,
                "Obter credencial Git para fetch/push seguro",
                "fetch falhou por credencial/autenticacao; nao ha como provar estado remoto atual",
                preconditions=("configurar credencial HTTPS/SSH do Gitea", "reexecutar fetch nao destrutivo"),
                evidence=(obs.environment_id, fetch),
            )
        )
    for obs in environment_blocked_observations(summary):
        operations.append(
            planned_operation(
                target,
                ReconciliationStatus.ENVIRONMENT_BLOCKED,
                ReceiptSeverity.BLOCKER,
                "Executar inventario no ambiente inacessivel",
                "ambiente declarado pela OS nao esta acessivel a partir desta sessao",
                preconditions=("abrir sessao no host correspondente", "rodar mais_humana.cli repo-mesh localmente"),
                evidence=(obs.environment_id, obs.expected_path, obs.nomenclature_note or ""),
            )
        )
    for obs in missing_observations(summary):
        operations.append(
            planned_operation(
                target,
                ReconciliationStatus.MISSING_MATERIALIZATION,
                ReceiptSeverity.WARNING,
                "Materializar espelho ausente sem sobrescrever destino",
                "repositorio declarado nao existe no ambiente observado",
                commands=(f"git clone {target.expected_remote_url} {obs.expected_path}",),
                preconditions=("confirmar que o destino ainda nao existe", "confirmar credencial Git", "registrar hash remoto antes do clone"),
                rollback=(f"remover clone recem-criado somente se vazio e sem alteracao local: {obs.expected_path}",),
                evidence=(obs.environment_id, obs.expected_path),
            )
        )
    for item in remote_mismatch_actions(summary):
        operations.append(
            planned_operation(
                target,
                ReconciliationStatus.REMOTE_MISMATCH,
                ReceiptSeverity.WARNING,
                "Corrigir remote origin divergente",
                item.reason,
                commands=item.command_preview,
                preconditions=item.required_before,
                rollback=("git remote set-url origin <remote-anterior-registrado>",),
                evidence=item.evidence,
            )
        )
    for item in nominal_actions(summary):
        status = ReconciliationStatus.NOMINAL_RENAME_READY if item.can_execute_automatically else ReconciliationStatus.MANUAL_DECISION_REQUIRED
        severity = ReceiptSeverity.WARNING if item.can_execute_automatically else ReceiptSeverity.BLOCKER
        operations.append(
            planned_operation(
                target,
                status,
                severity,
                "Reconciliar nomenclatura local/canonica",
                item.reason,
                commands=item.command_preview,
                preconditions=item.required_before,
                rollback=(f"renomear de volta para alias anterior se validacao falhar: {target.aliases[0] if target.aliases else target.declared_name}",),
                evidence=item.evidence,
                automatic=item.can_execute_automatically,
            )
        )
    for decision in naming:
        if decision.status != NamingStatus.CONFLICT:
            continue
        operations.append(
            planned_operation(
                target,
                ReconciliationStatus.MANUAL_DECISION_REQUIRED,
                ReceiptSeverity.BLOCKER,
                "Resolver conflito nominal antes de sincronizar",
                decision.reason,
                preconditions=("comparar HEAD de cada variante nominal", "decidir pasta canonica", "bloquear clone/rename automatico"),
                evidence=tuple(
                    f"{candidate.name}:{candidate.path}:{'exists' if candidate.exists else 'missing'}"
                    for candidate in decision.candidates
                ),
            )
        )
    if latest.precedence == StatePrecedence.SINGLE_HASH and not operations:
        operations.append(
            planned_operation(
                target,
                ReconciliationStatus.ALIGNED,
                ReceiptSeverity.INFO,
                "Manter fetch periodico nao destrutivo",
                "todos os ambientes com HEAD confirmado usam o mesmo commit",
                commands=("git fetch --all --prune",),
                preconditions=("manter worktree limpo", "bloquear se aparecer ahead/behind ou dirty tree"),
                evidence=(latest.selected_head or "", latest.selected_environment or ""),
                automatic=True,
            )
        )
    elif latest.precedence == StatePrecedence.REMOTE_AHEAD:
        operations.append(
            planned_operation(
                target,
                ReconciliationStatus.FAST_FORWARD_READY,
                ReceiptSeverity.WARNING,
                "Aplicar apenas fast-forward comprovado",
                latest.reason,
                commands=("git fetch --all --prune", "git merge --ff-only @{u}"),
                preconditions=("working tree limpo", "upstream configurado", "merge-base confirma fast-forward"),
                rollback=("nao ha rollback automatico; criar tag/recibo antes do ff-only se necessario",),
                evidence=(latest.selected_head or "", latest.selected_environment or ""),
                automatic=False,
            )
        )
    elif latest.has_blocker and not any(op.severity == ReceiptSeverity.BLOCKER for op in operations):
        operations.append(
            planned_operation(
                target,
                ReconciliationStatus.MANUAL_DECISION_REQUIRED,
                ReceiptSeverity.BLOCKER,
                "Decidir precedencia manualmente",
                latest.reason,
                preconditions=("comparar commits e timestamps", "validar qual alteracao e mais recente e valida"),
                evidence=latest.blockers,
            )
        )
    return tuple(operations)


def receipt_status(summary: MeshRepositorySummary, latest: LatestStateDecision, operations: Sequence[PlannedOperation]) -> ReconciliationStatus:
    if any(op.status == ReconciliationStatus.DIRTY_BLOCKED for op in operations):
        return ReconciliationStatus.DIRTY_BLOCKED
    if any(op.status == ReconciliationStatus.CREDENTIAL_BLOCKED for op in operations):
        return ReconciliationStatus.CREDENTIAL_BLOCKED
    if any(op.status == ReconciliationStatus.ENVIRONMENT_BLOCKED for op in operations):
        return ReconciliationStatus.ENVIRONMENT_BLOCKED
    if any(op.status == ReconciliationStatus.MANUAL_DECISION_REQUIRED and "nominal" in op.title.lower() for op in operations):
        return ReconciliationStatus.MANUAL_DECISION_REQUIRED
    if any(op.status == ReconciliationStatus.REMOTE_MISMATCH for op in operations):
        return ReconciliationStatus.REMOTE_MISMATCH
    if any(op.status == ReconciliationStatus.NOMINAL_RENAME_READY for op in operations):
        return ReconciliationStatus.NOMINAL_RENAME_READY
    if any(op.status == ReconciliationStatus.MISSING_MATERIALIZATION for op in operations):
        return ReconciliationStatus.MISSING_MATERIALIZATION
    if any(op.status == ReconciliationStatus.MANUAL_DECISION_REQUIRED for op in operations):
        return ReconciliationStatus.MANUAL_DECISION_REQUIRED
    if latest.precedence == StatePrecedence.SINGLE_HASH and summary.risk == MeshRiskLevel.OK:
        return ReconciliationStatus.ALIGNED
    if latest.precedence == StatePrecedence.REMOTE_AHEAD:
        return ReconciliationStatus.FAST_FORWARD_READY
    if latest.has_blocker:
        return ReconciliationStatus.MANUAL_DECISION_REQUIRED
    return ReconciliationStatus.UNKNOWN


def build_receipt(summary: MeshRepositorySummary) -> ReconciliationReceipt:
    latest = select_latest_state(summary)
    naming = naming_decisions(summary)
    operations = operations_from_summary(summary, latest, naming)
    status = receipt_status(summary, latest, operations)
    blockers = tuple(op.reason for op in operations if op.severity == ReceiptSeverity.BLOCKER)
    pending = merge_unique(
        list(blockers)
        + [op.reason for op in operations if op.severity == ReceiptSeverity.WARNING]
        + list(latest.blockers)
    )
    safe = (
        status == ReconciliationStatus.ALIGNED
        and summary.risk == MeshRiskLevel.OK
        and not blockers
        and latest.precedence == StatePrecedence.SINGLE_HASH
    )
    seed = {
        "target": summary.target.declared_name,
        "status": status.value,
        "head": latest.selected_head,
        "naming": [decision.status.value for decision in naming],
        "operations": [op.operation_id for op in operations],
    }
    return ReconciliationReceipt(
        receipt_id=f"mesh-receipt-{stable_digest(seed, 12)}",
        target_name=summary.target.declared_name,
        expected_local_name=summary.target.expected_local_name,
        gitea_repo=summary.target.gitea_repo,
        status=status,
        risk=summary.risk,
        latest_state=latest,
        naming_decisions=naming,
        operations=operations,
        pending_items=pending,
        safe_to_auto_sync=safe,
    )


def build_reconciliation_plan(report: MeshReport) -> ReconciliationPlan:
    receipts = tuple(build_receipt(summary) for summary in report.summaries)
    summary = (
        f"recibos: {len(receipts)}",
        f"auto_sync_ready: {sum(1 for item in receipts if item.safe_to_auto_sync)}",
        f"bloqueados: {sum(1 for item in receipts if item.blockers)}",
        f"renomes_prontos: {sum(1 for item in receipts if item.status == ReconciliationStatus.NOMINAL_RENAME_READY)}",
        f"credenciais_pendentes: {sum(1 for item in receipts if item.status == ReconciliationStatus.CREDENTIAL_BLOCKED)}",
    )
    seed = {"report": report.report_id, "receipts": [receipt.receipt_id for receipt in receipts]}
    return ReconciliationPlan(
        plan_id=f"mesh-plan-{stable_digest(seed, 12)}",
        report_id=report.report_id,
        generated_at=utc_now(),
        receipts=receipts,
        summary=summary,
    )


def reconciliation_rows(plan: ReconciliationPlan) -> list[list[str]]:
    rows = [
        [
            "receipt_id",
            "target_name",
            "expected_local_name",
            "status",
            "risk",
            "safe_to_auto_sync",
            "selected_head",
            "selected_environment",
            "precedence",
            "naming_status",
            "pending_count",
            "operation_count",
        ]
    ]
    for receipt in plan.receipts:
        rows.append(
            [
                receipt.receipt_id,
                receipt.target_name,
                receipt.expected_local_name,
                receipt.status.value,
                receipt.risk.value,
                "yes" if receipt.safe_to_auto_sync else "no",
                receipt.latest_state.selected_head or "",
                receipt.latest_state.selected_environment or "",
                receipt.latest_state.precedence.value,
                " | ".join(decision.status.value for decision in receipt.naming_decisions),
                str(len(receipt.pending_items)),
                str(len(receipt.operations)),
            ]
        )
    return rows


def operation_rows(plan: ReconciliationPlan) -> list[list[str]]:
    rows = [
        [
            "operation_id",
            "target_name",
            "status",
            "severity",
            "automatic",
            "destructive",
            "title",
            "reason",
            "commands",
            "preconditions",
            "rollback",
            "evidence",
        ]
    ]
    for receipt in plan.receipts:
        for operation in receipt.operations:
            rows.append(
                [
                    operation.operation_id,
                    operation.target_name,
                    operation.status.value,
                    operation.severity.value,
                    "yes" if operation.automatic else "no",
                    "yes" if operation.destructive else "no",
                    operation.title,
                    operation.reason,
                    " | ".join(operation.commands),
                    " | ".join(operation.preconditions),
                    " | ".join(operation.rollback),
                    " | ".join(operation.evidence),
                ]
            )
    return rows


def reconciliation_csv(plan: ReconciliationPlan) -> str:
    return rows_to_csv(reconciliation_rows(plan))


def operations_csv(plan: ReconciliationPlan) -> str:
    return rows_to_csv(operation_rows(plan))


def reconciliation_markdown(plan: ReconciliationPlan) -> str:
    lines = [
        "# Repository Mesh Reconciliation Plan",
        "",
        f"- plan_id: `{plan.plan_id}`",
        f"- report_id: `{plan.report_id}`",
        f"- generated_at: `{plan.generated_at}`",
        f"- blockers: `{plan.blocker_count}`",
        f"- auto_sync_ready: `{plan.auto_sync_ready_count}`",
        f"- rename_ready: `{plan.rename_ready_count}`",
        f"- credential_blocked: `{plan.credential_blocked_count}`",
        "",
        "## Sumario",
        "",
    ]
    lines.extend(f"- {item}" for item in plan.summary)
    lines.extend(["", "## Recibos", ""])
    for receipt in plan.receipts:
        latest = receipt.latest_state
        lines.append(f"### {receipt.target_name}")
        lines.append("")
        lines.append(f"- receipt_id: `{receipt.receipt_id}`")
        lines.append(f"- status: `{receipt.status.value}`")
        lines.append(f"- risk: `{receipt.risk.value}`")
        lines.append(f"- safe_to_auto_sync: `{receipt.safe_to_auto_sync}`")
        lines.append(f"- selected_head: `{latest.selected_head or ''}`")
        lines.append(f"- selected_environment: `{latest.selected_environment or ''}`")
        lines.append(f"- precedence: `{latest.precedence.value}`")
        lines.append(f"- reason: {latest.reason}")
        if receipt.naming_decisions:
            lines.append("- nomenclatura:")
            for decision in receipt.naming_decisions:
                lines.append(
                    f"  - `{decision.status.value}` selected=`{decision.selected_name or ''}` "
                    f"expected=`{decision.expected_name}` reason={decision.reason}"
                )
        if receipt.pending_items:
            lines.append("- pendencias:")
            for item in receipt.pending_items[:12]:
                lines.append(f"  - {item}")
        if receipt.operations:
            lines.append("- operacoes:")
            for operation in receipt.operations[:12]:
                auto = "auto" if operation.automatic else "manual"
                lines.append(f"  - `{operation.status.value}` `{operation.severity.value}` `{auto}`: {operation.title}")
        lines.append("")
    return "\n".join(lines).strip() + "\n"


def reconciliation_payload(plan: ReconciliationPlan) -> dict[str, Any]:
    return {
        "planId": plan.plan_id,
        "reportId": plan.report_id,
        "generatedAt": plan.generated_at,
        "blockerCount": plan.blocker_count,
        "autoSyncReadyCount": plan.auto_sync_ready_count,
        "renameReadyCount": plan.rename_ready_count,
        "credentialBlockedCount": plan.credential_blocked_count,
        "receipts": [receipt.to_dict() for receipt in plan.receipts],
    }


def receipt_to_service_order(receipt: ReconciliationReceipt, index: int, order_type: OrderType) -> ServiceOrder:
    """Convert a receipt into a concrete service-order object."""

    pending = "; ".join(receipt.pending_items[:6]) or "manter sincronizacao segura sem pendencia material"
    title = {
        OrderType.EXECUTIVE: f"Reconciliar espelho {receipt.expected_local_name}",
        OrderType.MANAGERIAL: f"Governar sincronizacao de {receipt.expected_local_name}",
    }[order_type]
    prefix = "EXECUTIVA" if order_type == OrderType.EXECUTIVE else "GERENCIAL"
    return ServiceOrder(
        order_id=incrementing_id(prefix, index, title),
        order_type=order_type,
        project_id="tudo-para-ia-mais-humana",
        title=title,
        purpose="Garantir que a malha de repositorios preserve a alteracao valida mais recente sem sobrescrever estado local ou remoto.",
        object_scope=(
            f"Repositorio `{receipt.gitea_repo}`, espelho local `{receipt.expected_local_name}`, "
            f"recibo `{receipt.receipt_id}` e plano `{receipt.status.value}`."
        ),
        reason=pending,
        expected_result=(
            "Espelho inventariado, bloqueios resolvidos ou formalizados, hashes comparados, "
            "e sincronizacao automatica mantida apenas quando segura."
        ),
        affected_paths=(
            f"G:/_codex-git/{receipt.expected_local_name}",
            "G:/codex_vm/_codex-git",
            "/root/__gpt-codex",
            "/root/_codex-git",
        ),
        validations=(
            "python -m mais_humana.cli repo-mesh --fetch",
            "validar que nao houve reset/checkout/clean/pull destrutivo",
            "comparar HEAD e status --short",
            "registrar pendencias no SQLite semantico",
        ),
        ready_criteria=(
            "hashes comparados",
            "working trees limpos ou pendencia registrada",
            "remote origin validado",
            "ambientes inacessiveis formalizados",
        ),
        status=OrderStatus.PLANNED,
        priority="alta" if receipt.blockers else "media",
    )


def service_orders_from_plan(plan: ReconciliationPlan, *, start_executive: int = 100, start_managerial: int = 100) -> tuple[ServiceOrder, ...]:
    """Create focused service orders from the highest-risk receipts."""

    receipts = sorted(
        plan.receipts,
        key=lambda item: (
            0 if item.blockers else 1,
            item.status.value,
            item.target_name,
        ),
    )
    executive: list[ServiceOrder] = []
    managerial: list[ServiceOrder] = []
    for receipt in receipts:
        if len(executive) < 5:
            executive.append(receipt_to_service_order(receipt, start_executive + len(executive), OrderType.EXECUTIVE))
        if len(managerial) < 5:
            managerial.append(receipt_to_service_order(receipt, start_managerial + len(managerial), OrderType.MANAGERIAL))
        if len(executive) >= 5 and len(managerial) >= 5:
            break
    return tuple(executive + managerial)


def service_orders_markdown(orders: Sequence[ServiceOrder]) -> str:
    lines = ["# Ordens de saida derivadas do Repository Mesh", ""]
    for order in orders:
        lines.append(f"## {order.order_id}")
        lines.append("")
        lines.append(f"- tipo: `{order.order_type.value}`")
        lines.append(f"- prioridade: `{order.priority}`")
        lines.append(f"- titulo: {order.title}")
        lines.append(f"- finalidade: {order.purpose}")
        lines.append(f"- objeto: {order.object_scope}")
        lines.append(f"- motivo: {order.reason}")
        lines.append("- validacoes:")
        for validation in order.validations:
            lines.append(f"  - `{validation}`")
        lines.append("")
    return "\n".join(lines).strip() + "\n"


def write_json(path: Path, payload: object) -> Path:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(as_plain_data(payload), ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8")
    return path


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


def write_reconciliation_artifacts(
    plan: ReconciliationPlan,
    project_root: Path,
    *,
    central_platform_folder: Path | None = None,
) -> tuple[GeneratedFile, ...]:
    """Write reconciliation artifacts and return semantic file records."""

    write_json(project_root / "dados" / "repository-mesh-reconciliation.json", plan)
    write_json(project_root / "dados" / "repository-mesh-reconciliation-summary.json", reconciliation_payload(plan))
    write_text(project_root / "matrizes" / "repository-mesh-reconciliation.csv", reconciliation_csv(plan))
    write_text(project_root / "matrizes" / "repository-mesh-operations.csv", operations_csv(plan))
    write_text(project_root / "ecossistema" / "REPOSITORY-MESH-RECONCILIATION.md", reconciliation_markdown(plan))
    orders = service_orders_from_plan(plan)
    write_json(project_root / "dados" / "repository-mesh-derived-orders.json", orders)
    write_text(project_root / "os-orientadoras" / "repository-mesh-derived-orders.md", service_orders_markdown(orders))
    central_written: list[Path] = []
    if central_platform_folder is not None:
        reports = central_platform_folder / "reports"
        indexes = central_platform_folder / "indexes"
        central_targets = (
            (reports / "EXECUTADO__repository-mesh-reconciliation.md", reconciliation_markdown(plan)),
            (indexes / "repository-mesh-reconciliation-index.md", service_orders_markdown(orders)),
        )
        for path, text in central_targets:
            try:
                write_text(path, text)
            except OSError:
                continue
            central_written.append(path)
    records = [
        GeneratedFile(
            path="dados/repository-mesh-reconciliation.json",
            description="Plano completo de reconciliacao segura da malha.",
            function="repository mesh reconciliation",
            file_type="json",
            changed_by="mais_humana.repository_mesh_reconciliation",
            change_summary="Gerado plano de precedencia, recibos e operacoes seguras.",
            relation_to_order="000_sincronizacao-dos-espelhos",
        ),
        GeneratedFile(
            path="dados/repository-mesh-reconciliation-summary.json",
            description="Resumo compacto de reconciliacao da malha.",
            function="repository mesh reconciliation summary",
            file_type="json",
            changed_by="mais_humana.repository_mesh_reconciliation",
            change_summary="Gerado resumo de blockers, renomes e credenciais.",
            relation_to_order="000_sincronizacao-dos-espelhos",
        ),
        GeneratedFile(
            path="matrizes/repository-mesh-reconciliation.csv",
            description="Tabela de recibos de reconciliacao.",
            function="repository mesh reconciliation matrix",
            file_type="csv",
            changed_by="mais_humana.repository_mesh_reconciliation",
            change_summary="Gerada matriz de recibos por repositorio.",
            relation_to_order="000_sincronizacao-dos-espelhos",
        ),
        GeneratedFile(
            path="matrizes/repository-mesh-operations.csv",
            description="Tabela de operacoes planejadas e bloqueios.",
            function="repository mesh operations matrix",
            file_type="csv",
            changed_by="mais_humana.repository_mesh_reconciliation",
            change_summary="Gerada matriz de operacoes seguras.",
            relation_to_order="000_sincronizacao-dos-espelhos",
        ),
        GeneratedFile(
            path="ecossistema/REPOSITORY-MESH-RECONCILIATION.md",
            description="Relatorio humano do plano de reconciliacao.",
            function="repository mesh reconciliation report",
            file_type="markdown",
            changed_by="mais_humana.repository_mesh_reconciliation",
            change_summary="Documentado plano de reconciliacao segura.",
            relation_to_order="000_sincronizacao-dos-espelhos",
        ),
        GeneratedFile(
            path="dados/repository-mesh-derived-orders.json",
            description="Ordens derivadas dos recibos de reconciliacao.",
            function="repository mesh derived orders",
            file_type="json",
            changed_by="mais_humana.repository_mesh_reconciliation",
            change_summary="Criadas ordens de saida executivas e gerenciais derivadas.",
            relation_to_order="000_sincronizacao-dos-espelhos",
        ),
        GeneratedFile(
            path="os-orientadoras/repository-mesh-derived-orders.md",
            description="Ordens derivadas em Markdown.",
            function="repository mesh derived orders",
            file_type="markdown",
            changed_by="mais_humana.repository_mesh_reconciliation",
            change_summary="Criado resumo humano das ordens derivadas.",
            relation_to_order="000_sincronizacao-dos-espelhos",
        ),
    ]
    for central_path in central_written:
        records.append(
            GeneratedFile(
                path=str(central_path),
                description="Registro central de reconciliacao escrito quando ACL permitiu.",
                function="repository mesh central reconciliation",
                file_type="markdown",
                changed_by="mais_humana.repository_mesh_reconciliation",
                change_summary="Registrado artefato central da reconciliacao sem abortar em ACL parcial.",
                relation_to_order="000_sincronizacao-dos-espelhos",
            )
        )
    return tuple(records)


def apply_reconciliation_to_report(
    report: MeshReport,
    project_root: Path,
    *,
    central_platform_folder: Path | None = None,
) -> tuple[ReconciliationPlan, tuple[GeneratedFile, ...]]:
    """Build and write reconciliation artifacts for a mesh report."""

    plan = build_reconciliation_plan(report)
    records = write_reconciliation_artifacts(plan, project_root, central_platform_folder=central_platform_folder)
    return plan, records
