"""Readiness gates for the repository mesh synchronization order."""

from __future__ import annotations

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

from .models import GeneratedFile, as_plain_data, clamp_score, merge_unique, utc_now
from .repository_mesh import MeshReport, MeshRiskLevel, rows_to_csv, stable_digest
from .repository_mesh_naming import NamingStatus
from .repository_mesh_reconciliation import ReconciliationPlan, ReconciliationStatus
from .repository_mesh_runtime import RuntimeCycle, SchedulerSpec
from .repository_mesh_semantic import MeshSemanticCounts


class MeshGateStatus(str, Enum):
    """Gate status for readiness evaluation."""

    PASS = "pass"
    WARN = "warn"
    FAIL = "fail"


class MeshGateSeverity(str, Enum):
    """Gate severity."""

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


@dataclass(frozen=True, slots=True)
class MeshReadinessGate:
    gate_id: str
    title: str
    status: MeshGateStatus
    severity: MeshGateSeverity
    score: int
    reason: str
    evidence: tuple[str, ...]
    next_action: str

    @property
    def passed(self) -> bool:
        return self.status == MeshGateStatus.PASS

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


@dataclass(frozen=True, slots=True)
class MeshReadinessReport:
    readiness_id: str
    report_id: str
    plan_id: str
    cycle_id: str
    generated_at: str
    score: int
    status: MeshGateStatus
    gates: tuple[MeshReadinessGate, ...]
    summary: tuple[str, ...]

    @property
    def blocker_count(self) -> int:
        return sum(1 for gate in self.gates if gate.status == MeshGateStatus.FAIL and gate.severity == MeshGateSeverity.BLOCKER)

    @property
    def warning_count(self) -> int:
        return sum(1 for gate in self.gates if gate.status == MeshGateStatus.WARN)

    @property
    def passed_count(self) -> int:
        return sum(1 for gate in self.gates if gate.passed)

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


def gate(
    gate_id: str,
    title: str,
    passed: bool,
    reason: str,
    next_action: str,
    *,
    evidence: Iterable[str] = (),
    severity: MeshGateSeverity = MeshGateSeverity.WARNING,
    warn: bool = False,
    score_pass: int = 100,
    score_fail: int = 0,
) -> MeshReadinessGate:
    if passed:
        status = MeshGateStatus.PASS
        score = score_pass
        actual_severity = MeshGateSeverity.INFO
    elif warn:
        status = MeshGateStatus.WARN
        score = max(1, min(89, score_fail or 55))
        actual_severity = severity
    else:
        status = MeshGateStatus.FAIL
        score = score_fail
        actual_severity = severity
    return MeshReadinessGate(
        gate_id=gate_id,
        title=title,
        status=status,
        severity=actual_severity,
        score=score,
        reason=reason,
        evidence=tuple(str(item) for item in evidence if str(item)),
        next_action=next_action,
    )


def inventory_gates(report: MeshReport) -> tuple[MeshReadinessGate, ...]:
    return (
        gate(
            "inventory.targets-declared",
            "Todos os repositorios declarados foram carregados",
            len(report.targets) >= 15,
            f"Repositorios declarados: {len(report.targets)}.",
            "atualizar manifesto da OS permanente",
            evidence=(str(len(report.targets)),),
            severity=MeshGateSeverity.BLOCKER,
        ),
        gate(
            "inventory.observations-created",
            "Observacoes por ambiente foram criadas",
            report.total_observations >= len(report.targets),
            f"Observacoes: {report.total_observations}.",
            "rodar inventario nos ambientes faltantes",
            evidence=(str(report.total_observations),),
            severity=MeshGateSeverity.BLOCKER,
        ),
        gate(
            "inventory.hashes-present",
            "Ao menos um hash HEAD foi observado",
            any(summary.hash_set for summary in report.summaries),
            "Hashes HEAD foram coletados em ambientes acessiveis." if any(summary.hash_set for summary in report.summaries) else "Nenhum hash HEAD foi coletado.",
            "corrigir acesso a repositorios e repetir git rev-parse HEAD",
            evidence=[summary.target.declared_name for summary in report.summaries if summary.hash_set][:8],
            severity=MeshGateSeverity.BLOCKER,
        ),
        gate(
            "inventory.fetch-attempted",
            "Fetch nao destrutivo foi tentado quando solicitado",
            report.fetch_enabled,
            "Fetch habilitado no ciclo." if report.fetch_enabled else "Inventario foi executado sem fetch.",
            "executar repo-mesh --fetch em rodada com credencial Git disponivel",
            evidence=(f"fetch_enabled={report.fetch_enabled}",),
            severity=MeshGateSeverity.WARNING,
            warn=not report.fetch_enabled,
            score_fail=60,
        ),
    )


def safety_gates(report: MeshReport, plan: ReconciliationPlan, cycle: RuntimeCycle) -> tuple[MeshReadinessGate, ...]:
    destructive_allowed = [
        result.command.command
        for result in cycle.results
        if result.status.value in {"allowed", "executed"} and result.command.destructive
    ]
    dirty = report.dirty_repositories
    credential = report.credential_errors
    blocker_receipts = plan.blocker_count
    return (
        gate(
            "safety.no-destructive-auto",
            "Nenhum comando destrutivo ficou permitido automaticamente",
            not destructive_allowed,
            "Runtime nao permitiu comando destrutivo." if not destructive_allowed else "Ha comando destrutivo permitido.",
            "corrigir gates do runtime antes de ativar scheduler",
            evidence=destructive_allowed[:8],
            severity=MeshGateSeverity.BLOCKER,
        ),
        gate(
            "safety.dirty-blocked",
            "Working trees sujos viraram bloqueio",
            dirty == 0 or any(receipt.status == ReconciliationStatus.DIRTY_BLOCKED for receipt in plan.receipts),
            f"Worktrees sujos detectados: {dirty}.",
            "commit/stash consciente ou decisao de precedencia",
            evidence=(str(dirty),),
            severity=MeshGateSeverity.BLOCKER,
        ),
        gate(
            "safety.credentials-classified",
            "Falhas de credencial foram classificadas",
            credential == 0 or plan.credential_blocked_count >= credential,
            f"Falhas de credencial: {credential}; recibos credential_blocked: {plan.credential_blocked_count}.",
            "configurar credencial Git e repetir fetch",
            evidence=(str(credential), str(plan.credential_blocked_count)),
            severity=MeshGateSeverity.BLOCKER,
        ),
        gate(
            "safety.receipts-created",
            "Recibos de reconciliacao foram criados",
            len(plan.receipts) == len(report.summaries),
            f"Recibos: {len(plan.receipts)}; repositorios: {len(report.summaries)}.",
            "regerar plano de reconciliacao",
            evidence=(str(len(plan.receipts)), str(len(report.summaries))),
            severity=MeshGateSeverity.BLOCKER,
        ),
        gate(
            "safety.blockers-visible",
            "Bloqueios estao visiveis no plano",
            blocker_receipts == 0,
            f"Recibos com blocker: {blocker_receipts}.",
            "executar ordens de saida para remover bloqueios reais",
            evidence=(str(blocker_receipts),),
            severity=MeshGateSeverity.WARNING,
            warn=blocker_receipts > 0,
            score_fail=max(10, 80 - blocker_receipts * 5),
        ),
    )


def naming_gates(plan: ReconciliationPlan) -> tuple[MeshReadinessGate, ...]:
    decisions = [decision for receipt in plan.receipts for decision in receipt.naming_decisions]
    conflicts = [decision for decision in decisions if decision.status == NamingStatus.CONFLICT]
    pending = [decision for decision in decisions if decision.status not in {NamingStatus.EXACT}]
    return (
        gate(
            "naming.policy-evaluated",
            "Politica de nomenclatura avaliou os repositorios",
            bool(decisions),
            f"Decisoes nominais: {len(decisions)}.",
            "executar reconciliacao com raizes acessiveis",
            evidence=(str(len(decisions)),),
            severity=MeshGateSeverity.BLOCKER,
        ),
        gate(
            "naming.no-conflicts",
            "Conflitos de nome foram bloqueados",
            not conflicts,
            f"Conflitos nominais: {len(conflicts)}.",
            "comparar variantes platform/plataform/alias antes de rename ou clone",
            evidence=[decision.target_name for decision in conflicts][:8],
            severity=MeshGateSeverity.BLOCKER,
        ),
        gate(
            "naming.pending-visible",
            "Pendencias nominais estao visiveis",
            not pending,
            f"Pendencias nominais: {len(pending)}.",
            "executar ordens de renome/alias geradas pela reconciliacao",
            evidence=[f"{decision.target_name}:{decision.status.value}" for decision in pending][:8],
            severity=MeshGateSeverity.WARNING,
            warn=bool(pending),
            score_fail=max(10, 85 - len(pending) * 5),
        ),
    )


def automation_gates(cycle: RuntimeCycle, schedulers: Sequence[SchedulerSpec]) -> tuple[MeshReadinessGate, ...]:
    kinds = {spec.kind.value for spec in schedulers}
    return (
        gate(
            "automation.runtime-cycle",
            "Ciclo runtime foi gerado",
            bool(cycle.cycle_id),
            f"Ciclo: {cycle.cycle_id}.",
            "gerar runtime cycle com lock e comandos",
            evidence=(cycle.cycle_id,),
            severity=MeshGateSeverity.BLOCKER,
        ),
        gate(
            "automation.lock-held",
            "Runtime registrou estado de lock",
            bool(cycle.lock.status.value),
            f"Lock: {cycle.lock.status.value}.",
            "corrigir lock do runtime",
            evidence=(cycle.lock.path, cycle.lock.status.value),
            severity=MeshGateSeverity.BLOCKER,
        ),
        gate(
            "automation.schedulers",
            "Schedulers Windows e Linux foram especificados",
            {"windows_task", "cron"}.issubset(kinds),
            f"Schedulers: {', '.join(sorted(kinds))}.",
            "publicar instalacao em cada ambiente",
            evidence=sorted(kinds),
            severity=MeshGateSeverity.WARNING,
            warn=not {"windows_task", "cron"}.issubset(kinds),
            score_fail=70,
        ),
        gate(
            "automation.allowed-commands-limited",
            "Comandos automaticos permitidos sao minoria controlada",
            cycle.allowed_count <= max(1, len(cycle.commands)),
            f"Allowed: {cycle.allowed_count}; total: {len(cycle.commands)}.",
            "revisar gates se comandos automaticos crescerem sem recibo",
            evidence=(str(cycle.allowed_count), str(len(cycle.commands))),
            severity=MeshGateSeverity.WARNING,
        ),
    )


def semantic_gates(counts: MeshSemanticCounts) -> tuple[MeshReadinessGate, ...]:
    return (
        gate(
            "semantic.report",
            "SQL registrou report da malha",
            counts.reports > 0,
            f"Reports SQL: {counts.reports}.",
            "executar write_repository_mesh_semantic_state",
            evidence=(str(counts.reports),),
            severity=MeshGateSeverity.BLOCKER,
        ),
        gate(
            "semantic.receipts",
            "SQL registrou recibos de reconciliacao",
            counts.receipts > 0,
            f"Receipts SQL: {counts.receipts}.",
            "persistir plano de reconciliacao no SQLite",
            evidence=(str(counts.receipts),),
            severity=MeshGateSeverity.BLOCKER,
        ),
        gate(
            "semantic.schedulers",
            "SQL registrou schedulers",
            counts.schedulers >= 2,
            f"Schedulers SQL: {counts.schedulers}.",
            "persistir specs de scheduler Windows/Cron",
            evidence=(str(counts.schedulers),),
            severity=MeshGateSeverity.WARNING,
            warn=counts.schedulers < 2,
            score_fail=70,
        ),
    )


def build_mesh_readiness_report(
    report: MeshReport,
    plan: ReconciliationPlan,
    cycle: RuntimeCycle,
    schedulers: Sequence[SchedulerSpec],
    counts: MeshSemanticCounts,
) -> MeshReadinessReport:
    gates = (
        inventory_gates(report)
        + safety_gates(report, plan, cycle)
        + naming_gates(plan)
        + automation_gates(cycle, schedulers)
        + semantic_gates(counts)
    )
    score = clamp_score(sum(gate.score for gate in gates) / len(gates)) if gates else 0
    blocker_count = sum(1 for gate in gates if gate.status == MeshGateStatus.FAIL and gate.severity == MeshGateSeverity.BLOCKER)
    warning_count = sum(1 for gate in gates if gate.status == MeshGateStatus.WARN)
    if blocker_count:
        status = MeshGateStatus.FAIL
    elif warning_count:
        status = MeshGateStatus.WARN
    else:
        status = MeshGateStatus.PASS
    summary = (
        f"score: {score}",
        f"status: {status.value}",
        f"gates: {len(gates)}",
        f"blockers: {blocker_count}",
        f"warnings: {warning_count}",
        f"passed: {sum(1 for item in gates if item.passed)}",
    )
    seed = {"report": report.report_id, "plan": plan.plan_id, "cycle": cycle.cycle_id, "score": score}
    return MeshReadinessReport(
        readiness_id=f"mesh-readiness-{stable_digest(seed, 12)}",
        report_id=report.report_id,
        plan_id=plan.plan_id,
        cycle_id=cycle.cycle_id,
        generated_at=utc_now(),
        score=score,
        status=status,
        gates=gates,
        summary=summary,
    )


def readiness_rows(readiness: MeshReadinessReport) -> list[list[str]]:
    rows = [["gate_id", "status", "severity", "score", "title", "reason", "next_action", "evidence"]]
    for gate in readiness.gates:
        rows.append(
            [
                gate.gate_id,
                gate.status.value,
                gate.severity.value,
                str(gate.score),
                gate.title,
                gate.reason,
                gate.next_action,
                " | ".join(gate.evidence),
            ]
        )
    return rows


def readiness_csv(readiness: MeshReadinessReport) -> str:
    return rows_to_csv(readiness_rows(readiness))


def readiness_markdown(readiness: MeshReadinessReport) -> str:
    lines = [
        "# Repository Mesh Readiness",
        "",
        f"- readiness_id: `{readiness.readiness_id}`",
        f"- report_id: `{readiness.report_id}`",
        f"- plan_id: `{readiness.plan_id}`",
        f"- cycle_id: `{readiness.cycle_id}`",
        f"- generated_at: `{readiness.generated_at}`",
        f"- status: `{readiness.status.value}`",
        f"- score: `{readiness.score}`",
        f"- blockers: `{readiness.blocker_count}`",
        f"- warnings: `{readiness.warning_count}`",
        "",
        "## Sumario",
        "",
    ]
    lines.extend(f"- {item}" for item in readiness.summary)
    lines.extend(["", "## Gates", ""])
    for gate in readiness.gates:
        lines.append(f"### {gate.gate_id}")
        lines.append("")
        lines.append(f"- status: `{gate.status.value}`")
        lines.append(f"- severity: `{gate.severity.value}`")
        lines.append(f"- score: `{gate.score}`")
        lines.append(f"- title: {gate.title}")
        lines.append(f"- reason: {gate.reason}")
        lines.append(f"- next_action: {gate.next_action}")
        if gate.evidence:
            lines.append("- evidence:")
            for item in gate.evidence[:10]:
                lines.append(f"  - `{item}`")
        lines.append("")
    return "\n".join(lines).strip() + "\n"


def readiness_pending_items(readiness: MeshReadinessReport) -> tuple[str, ...]:
    return merge_unique(
        f"{gate.gate_id}: {gate.next_action}"
        for gate in readiness.gates
        if gate.status in {MeshGateStatus.FAIL, MeshGateStatus.WARN}
    )


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_readiness_artifacts(
    readiness: MeshReadinessReport,
    project_root: Path,
    *,
    central_platform_folder: Path | None = None,
) -> tuple[GeneratedFile, ...]:
    write_json(project_root / "dados" / "repository-mesh-readiness.json", readiness)
    write_text(project_root / "matrizes" / "repository-mesh-readiness.csv", readiness_csv(readiness))
    write_text(project_root / "ecossistema" / "REPOSITORY-MESH-READINESS.md", readiness_markdown(readiness))
    central_written: list[Path] = []
    if central_platform_folder is not None:
        central_targets = (
            (central_platform_folder / "reports" / "EXECUTADO__repository-mesh-readiness.md", readiness_markdown(readiness)),
            (central_platform_folder / "indexes" / "repository-mesh-readiness-index.md", readiness_markdown(readiness)),
        )
        for path, text in central_targets:
            try:
                write_text(path, text)
            except OSError:
                continue
            central_written.append(path)
    records = [
        GeneratedFile(
            path="dados/repository-mesh-readiness.json",
            description="Readiness JSON da malha de repositorios.",
            function="repository mesh readiness",
            file_type="json",
            changed_by="mais_humana.repository_mesh_readiness",
            change_summary="Gerados gates de aceite da sincronizacao segura.",
            relation_to_order="000_sincronizacao-dos-espelhos",
        ),
        GeneratedFile(
            path="matrizes/repository-mesh-readiness.csv",
            description="Matriz de gates da malha de repositorios.",
            function="repository mesh readiness matrix",
            file_type="csv",
            changed_by="mais_humana.repository_mesh_readiness",
            change_summary="Gerada matriz de readiness da malha.",
            relation_to_order="000_sincronizacao-dos-espelhos",
        ),
        GeneratedFile(
            path="ecossistema/REPOSITORY-MESH-READINESS.md",
            description="Relatorio humano de readiness da malha.",
            function="repository mesh readiness report",
            file_type="markdown",
            changed_by="mais_humana.repository_mesh_readiness",
            change_summary="Documentados gates de aceite da OS permanente.",
            relation_to_order="000_sincronizacao-dos-espelhos",
        ),
    ]
    for central_path in central_written:
        records.append(
            GeneratedFile(
                path=str(central_path),
                description="Readiness da malha registrada na central quando ACL permitiu.",
                function="repository mesh central readiness",
                file_type="markdown",
                changed_by="mais_humana.repository_mesh_readiness",
                change_summary="Registrado artefato central de readiness sem abortar em ACL parcial.",
                relation_to_order="000_sincronizacao-dos-espelhos",
            )
        )
    return tuple(records)
