"""Governance evaluation engine for the Mais Humana platform."""

from __future__ import annotations

import csv
import io
from pathlib import Path
from typing import Iterable, Mapping, Sequence

from .catalog import PLATFORM_BY_ID
from .governance_catalog import CHECK_TEMPLATES, DOMAIN_TO_PRIMARY_PLATFORM, templates_for_platform
from .governance_models import (
    CheckTemplate,
    EcosystemGovernancePortfolio,
    GovernanceAxis,
    GovernanceCheckResult,
    GovernanceDomain,
    GovernanceEvidence,
    GovernanceEvidenceKind,
    GovernanceMaturity,
    GovernanceOrderCandidate,
    GovernanceSeverity,
    GovernanceStatus,
    PlatformGovernanceCard,
    dedupe_candidates,
    score_to_maturity,
    severity_rank,
    status_score,
)
from .models import EvidenceKind, OrderType, PlatformHumanReport, Recommendation, as_plain_data, merge_unique, slugify, utc_now
from .operational_models import ExecutionRoundDossier, PlatformOperationalDossier, ReadinessGate, SourceReference


def lower_terms(values: Iterable[str]) -> tuple[str, ...]:
    return tuple(str(value).lower() for value in values if str(value).strip())


def contains_term(text: str, term: str) -> bool:
    if not term:
        return False
    return term.lower() in text


def count_terms(text: str, terms: Iterable[str]) -> int:
    lowered = text.lower()
    return sum(1 for term in lower_terms(terms) if contains_term(lowered, term))


def matching_terms(text: str, terms: Iterable[str]) -> tuple[str, ...]:
    lowered = text.lower()
    return tuple(term for term in lower_terms(terms) if contains_term(lowered, term))


def corpus_for_report(
    report: PlatformHumanReport,
    recommendations: Sequence[Recommendation] = (),
    dossier: PlatformOperationalDossier | None = None,
    extra_text: Sequence[str] = (),
) -> str:
    parts: list[str] = [
        report.platform.platform_id,
        report.platform.title,
        report.platform.mission,
        report.scan.repo_path,
        report.scan.readme_excerpt,
        " ".join(report.platform.expected_surfaces),
        " ".join(report.platform.known_blockers),
        " ".join(report.scan.warnings),
    ]
    for evidence in report.scan.evidence[:400]:
        parts.append(evidence.path)
        parts.append(evidence.summary)
        parts.append(" ".join(evidence.tags))
        parts.append(evidence.kind.value)
    for recommendation in recommendations:
        if recommendation.platform_id == report.platform.platform_id:
            parts.extend(
                [
                    recommendation.recommendation_id,
                    recommendation.title,
                    recommendation.reason,
                    recommendation.expected_impact,
                    " ".join(path for path in recommendation.affected_paths),
                ]
            )
    if dossier is not None:
        parts.extend([dossier.stage.value, dossier.status_label, dossier.primary_action])
        for signal in dossier.signals:
            parts.extend(
                [
                    signal.signal_id,
                    signal.title,
                    signal.summary,
                    signal.next_action,
                    signal.kind.value,
                    signal.domain.value,
                    " ".join(signal.tags),
                ]
            )
            for source in signal.sources:
                parts.append(source.path)
                parts.append(source.summary)
        for gate in dossier.gates:
            parts.extend([gate.gate_id, gate.title, gate.outcome.value, gate.reason, gate.next_action])
    parts.extend(extra_text)
    return "\n".join(part for part in parts if part).lower()


def evidence_kind_from_scan(kind: EvidenceKind) -> GovernanceEvidenceKind:
    if kind == EvidenceKind.TEST:
        return GovernanceEvidenceKind.TEST
    if kind == EvidenceKind.OPENAPI:
        return GovernanceEvidenceKind.CONTRACT
    if kind in {EvidenceKind.README, EvidenceKind.DOC}:
        return GovernanceEvidenceKind.DOCUMENTATION
    if kind in {EvidenceKind.CONFIG, EvidenceKind.WORKER, EvidenceKind.STORAGE}:
        return GovernanceEvidenceKind.CONFIG
    if kind in {EvidenceKind.ROUTE, EvidenceKind.MCP_TOOL, EvidenceKind.UI_SURFACE}:
        return GovernanceEvidenceKind.CODE
    if kind in {EvidenceKind.SECURITY, EvidenceKind.OBSERVABILITY, EvidenceKind.BUSINESS_RULE}:
        return GovernanceEvidenceKind.RUNTIME
    return GovernanceEvidenceKind.DERIVED


def evidence_from_source(source: SourceReference) -> GovernanceEvidence:
    return GovernanceEvidence(
        path=source.path,
        line=source.line,
        summary=source.summary,
        kind=GovernanceEvidenceKind.DERIVED,
        confidence=0.7,
    )


def evidence_from_gate(gate: ReadinessGate) -> tuple[GovernanceEvidence, ...]:
    return tuple(evidence_from_source(source) for source in gate.evidence)


def evidence_from_report_terms(report: PlatformHumanReport, terms: Sequence[str], limit: int = 8) -> tuple[GovernanceEvidence, ...]:
    matches: list[GovernanceEvidence] = []
    lowered_terms = lower_terms(terms)
    if not lowered_terms:
        return ()
    for evidence in report.scan.evidence:
        text = f"{evidence.path} {evidence.summary} {' '.join(evidence.tags)} {evidence.kind.value}".lower()
        if any(term in text for term in lowered_terms):
            matches.append(
                GovernanceEvidence(
                    path=evidence.path,
                    line=evidence.line,
                    summary=evidence.summary,
                    kind=evidence_kind_from_scan(evidence.kind),
                    confidence=evidence.confidence,
                )
            )
        if len(matches) >= limit:
            return tuple(matches)
    for warning in report.scan.warnings:
        text = warning.lower()
        if any(term in text for term in lowered_terms):
            matches.append(
                GovernanceEvidence(
                    path=report.scan.repo_path,
                    summary=warning,
                    kind=GovernanceEvidenceKind.DERIVED,
                    confidence=0.65,
                )
            )
        if len(matches) >= limit:
            break
    return tuple(matches)


def direct_status_for_template(
    report: PlatformHumanReport,
    template: CheckTemplate,
    dossier: PlatformOperationalDossier | None,
) -> GovernanceStatus | None:
    check_id = template.check_id
    if check_id == "repository.exists":
        return GovernanceStatus.PASS if report.scan.exists else GovernanceStatus.BLOCKED
    if check_id == "repository.git-traceability":
        return GovernanceStatus.PASS if report.scan.git_present else GovernanceStatus.FAIL
    if check_id == "documentation.human-readme":
        return GovernanceStatus.PASS if report.scan.readme_excerpt else GovernanceStatus.ATTENTION
    if check_id == "tests.detected":
        return GovernanceStatus.PASS if report.scan.has_tests else GovernanceStatus.ATTENTION
    if check_id == "contract.openapi-or-equivalent":
        return GovernanceStatus.PASS if report.scan.has_openapi else GovernanceStatus.ATTENTION
    if check_id == "docs.canonical-read" and report.platform.platform_id == "docs":
        blocker_text = " ".join(report.platform.known_blockers).lower()
        if "catalogonly" in blocker_text or "catalog_only" in blocker_text:
            return GovernanceStatus.BLOCKED
    if check_id == "panel.same-source" and dossier is not None:
        if dossier.same_source_ready:
            return GovernanceStatus.PASS
        if dossier.panel_ready:
            return GovernanceStatus.ATTENTION
    if check_id == "panel.backend-mcp" and dossier is not None:
        if dossier.panel_ready:
            return GovernanceStatus.PASS
    if check_id == "observability.audit-trace" and dossier is not None:
        if any(gate.domain.value in {"observability", "governance"} and gate.passed for gate in dossier.gates):
            return GovernanceStatus.PASS
    return None


def choose_status(
    template: CheckTemplate,
    positive_hits: int,
    negative_hits: int,
    required_hits: int,
    direct: GovernanceStatus | None,
) -> GovernanceStatus:
    if direct is not None:
        return direct
    if negative_hits and template.check_id == "cloud.plugin-exception":
        return GovernanceStatus.FAIL
    if positive_hits and template.check_id == "cloud.plugin-exception":
        return GovernanceStatus.EXCEPTION
    if negative_hits:
        if template.required_for_human_ready or template.severity in {GovernanceSeverity.HIGH, GovernanceSeverity.CRITICAL}:
            return GovernanceStatus.BLOCKED
        return GovernanceStatus.FAIL
    if template.required_terms and required_hits < len(template.required_terms):
        if positive_hits:
            return GovernanceStatus.ATTENTION
        if template.required_for_human_ready:
            return GovernanceStatus.FAIL
        return GovernanceStatus.ATTENTION
    if positive_hits:
        return GovernanceStatus.PASS
    if template.required_for_human_ready:
        return GovernanceStatus.ATTENTION
    return GovernanceStatus.ATTENTION


def result_severity(template: CheckTemplate, status: GovernanceStatus) -> GovernanceSeverity:
    if status in {GovernanceStatus.PASS, GovernanceStatus.EXCEPTION, GovernanceStatus.NOT_APPLICABLE}:
        return GovernanceSeverity.INFO
    if status == GovernanceStatus.BLOCKED:
        if template.severity in {GovernanceSeverity.HIGH, GovernanceSeverity.CRITICAL}:
            return template.severity
        return GovernanceSeverity.HIGH
    if status == GovernanceStatus.FAIL:
        return template.severity
    if status == GovernanceStatus.ATTENTION:
        if template.severity == GovernanceSeverity.CRITICAL:
            return GovernanceSeverity.HIGH
        return template.severity
    return GovernanceSeverity.INFO


def reason_for_status(template: CheckTemplate, status: GovernanceStatus, positive: Sequence[str], negative: Sequence[str]) -> str:
    if status == GovernanceStatus.PASS:
        return template.pass_summary
    if status == GovernanceStatus.EXCEPTION:
        return template.pass_summary
    if status == GovernanceStatus.NOT_APPLICABLE:
        return template.not_applicable_reason
    if status == GovernanceStatus.ATTENTION:
        detail = f" Termos encontrados: {', '.join(positive[:5])}." if positive else ""
        return template.attention_summary + detail
    if status in {GovernanceStatus.FAIL, GovernanceStatus.BLOCKED}:
        detail = f" Sinais negativos: {', '.join(negative[:5])}." if negative else ""
        return template.fail_summary + detail
    return template.attention_summary


def check_score(status: GovernanceStatus, positive_hits: int, required_hits: int, negative_hits: int) -> int:
    base = status_score(status)
    if status == GovernanceStatus.NOT_APPLICABLE:
        return 100
    bonus = min(10, positive_hits * 2 + required_hits * 3)
    penalty = min(30, negative_hits * 8)
    return max(0, min(100, base + bonus - penalty))


def evaluate_template(
    report: PlatformHumanReport,
    template: CheckTemplate,
    recommendations: Sequence[Recommendation] = (),
    dossier: PlatformOperationalDossier | None = None,
    extra_text: Sequence[str] = (),
) -> GovernanceCheckResult:
    if not template.applies_to_platform(report.platform.platform_id):
        return GovernanceCheckResult(
            check_id=template.check_id,
            platform_id=report.platform.platform_id,
            axis=template.axis,
            domain=template.domain,
            title=template.title,
            status=GovernanceStatus.NOT_APPLICABLE,
            severity=GovernanceSeverity.INFO,
            maturity=GovernanceMaturity.INSTITUTIONAL,
            score=100,
            reason=template.not_applicable_reason,
            next_action="manter monitoramento se a relacao passar a existir",
            evidence=(),
            validation_steps=(),
            order_title=template.normalized_order_title,
            order_type=template.creates_order_type,
            order_priority=template.order_priority,
            required_for_human_ready=False,
        )
    corpus = corpus_for_report(report, recommendations, dossier, extra_text)
    positive = matching_terms(corpus, template.positive_terms)
    negative = matching_terms(corpus, template.negative_terms)
    required = matching_terms(corpus, template.required_terms)
    direct = direct_status_for_template(report, template, dossier)
    status = choose_status(template, len(positive), len(negative), len(required), direct)
    severity = result_severity(template, status)
    score = check_score(status, len(positive), len(required), len(negative))
    evidence_terms = tuple(template.evidence_hints) + positive + negative + required
    evidence = list(evidence_from_report_terms(report, evidence_terms))
    if dossier is not None:
        for gate in dossier.gates:
            gate_text = f"{gate.gate_id} {gate.title} {gate.reason} {gate.next_action}".lower()
            if template.domain.value in gate_text or any(term in gate_text for term in lower_terms(evidence_terms)):
                evidence.extend(evidence_from_gate(gate))
    if not evidence and status in {GovernanceStatus.FAIL, GovernanceStatus.BLOCKED}:
        evidence.append(
            GovernanceEvidence(
                path=report.scan.repo_path,
                summary="Ausencia ou sinal negativo inferido pelo check de governanca.",
                kind=GovernanceEvidenceKind.ABSENCE,
                confidence=0.55,
            )
        )
    reason = reason_for_status(template, status, positive, negative)
    return GovernanceCheckResult(
        check_id=template.check_id,
        platform_id=report.platform.platform_id,
        axis=template.axis,
        domain=template.domain,
        title=template.title,
        status=status,
        severity=severity,
        maturity=score_to_maturity(score),
        score=score,
        reason=reason,
        next_action=template.suggested_action if status != GovernanceStatus.PASS else "manter evidencia e regressao",
        evidence=tuple(evidence[:10]),
        validation_steps=template.validation_steps,
        order_title=template.normalized_order_title,
        order_type=template.creates_order_type,
        order_priority=template.order_priority,
        required_for_human_ready=template.required_for_human_ready,
    )


def card_score(checks: Sequence[GovernanceCheckResult]) -> int:
    applicable = [check for check in checks if check.status != GovernanceStatus.NOT_APPLICABLE]
    if not applicable:
        return 0
    weighted_total = 0
    total_weight = 0
    for check in applicable:
        weight = 2 if check.required_for_human_ready else 1
        if check.severity in {GovernanceSeverity.HIGH, GovernanceSeverity.CRITICAL}:
            weight += 1
        weighted_total += check.score * weight
        total_weight += weight
    return round(weighted_total / total_weight) if total_weight else 0


def relation_summary(report: PlatformHumanReport) -> tuple[str, ...]:
    lines: list[str] = []
    for related in report.platform.related_platforms:
        if related in PLATFORM_BY_ID:
            lines.append(f"{report.platform.platform_id} depende ou conversa com {related}")
    for category in report.platform.primary_categories:
        lines.append(f"categoria primaria: {category.value}")
    return merge_unique(lines)[:8]


def build_strengths(checks: Sequence[GovernanceCheckResult]) -> tuple[str, ...]:
    strengths = [
        f"{check.domain.value}: {check.title}"
        for check in checks
        if check.status in {GovernanceStatus.PASS, GovernanceStatus.EXCEPTION}
    ]
    return merge_unique(strengths)[:10]


def build_next_actions(checks: Sequence[GovernanceCheckResult], limit: int = 10) -> tuple[str, ...]:
    candidates = [
        check.next_action
        for check in sorted(checks, key=lambda item: (-severity_rank(item.severity), item.score, item.title))
        if check.status in {GovernanceStatus.ATTENTION, GovernanceStatus.FAIL, GovernanceStatus.BLOCKED}
    ]
    return merge_unique(candidates)[:limit]


def build_platform_governance_card(
    report: PlatformHumanReport,
    recommendations: Sequence[Recommendation] = (),
    dossier: PlatformOperationalDossier | None = None,
    extra_text: Sequence[str] = (),
) -> PlatformGovernanceCard:
    checks = tuple(
        evaluate_template(report, template, recommendations, dossier, extra_text)
        for template in templates_for_platform(report.platform.platform_id)
    )
    score = card_score(checks)
    blockers = tuple(check for check in checks if check.is_blocking)
    warnings = tuple(
        check
        for check in checks
        if check.status in {GovernanceStatus.ATTENTION, GovernanceStatus.FAIL}
        and not check.is_blocking
    )
    return PlatformGovernanceCard(
        platform_id=report.platform.platform_id,
        title=report.platform.title,
        repo_path=report.scan.repo_path,
        checks=checks,
        human_score=report.average_score,
        governance_score=score,
        maturity=score_to_maturity(score),
        blockers=blockers,
        warnings=warnings,
        strengths=build_strengths(checks),
        next_actions=build_next_actions(checks),
        relation_summary=relation_summary(report),
    )


def candidate_from_check(check: GovernanceCheckResult, repo_path: str, index: int) -> GovernanceOrderCandidate:
    prefix = "EXECUTIVA" if check.order_type == OrderType.EXECUTIVE else "GERENCIAL"
    candidate_id = f"{index:04d}_{prefix}__{slugify(check.order_title)}"
    return GovernanceOrderCandidate(
        candidate_id=candidate_id,
        platform_id=check.platform_id,
        order_type=check.order_type,
        title=check.order_title,
        purpose=(
            "Transformar check de governanca em continuidade executavel, "
            "com evidencia, validacao e criterio humano de pronto."
        ),
        reason=check.reason,
        expected_result=check.next_action,
        priority=check.order_priority,
        affected_paths=(repo_path,),
        validations=check.validation_steps,
        source_check_ids=(check.check_id,),
    )


def build_order_candidates(cards: Sequence[PlatformGovernanceCard]) -> tuple[GovernanceOrderCandidate, ...]:
    candidates: list[GovernanceOrderCandidate] = []
    index = 1
    for card in sorted(cards, key=lambda item: (item.governance_score, item.platform_id)):
        actionable = [
            check
            for check in card.checks
            if check.needs_order and check.status != GovernanceStatus.NOT_APPLICABLE
        ]
        actionable.sort(key=lambda item: (-severity_rank(item.severity), item.score, item.title))
        for check in actionable[:5]:
            candidates.append(candidate_from_check(check, card.repo_path, index))
            index += 1
    return dedupe_candidates(candidates)


def relation_matrix(cards: Sequence[PlatformGovernanceCard]) -> tuple[tuple[str, str, str], ...]:
    rows: list[tuple[str, str, str]] = []
    ids = {card.platform_id for card in cards}
    for card in cards:
        platform = PLATFORM_BY_ID.get(card.platform_id)
        if platform is None:
            continue
        for related in platform.related_platforms:
            relation = "declared" if related in ids else "external_or_missing"
            rows.append((card.platform_id, related, relation))
        for check in card.checks:
            if check.status == GovernanceStatus.NOT_APPLICABLE:
                continue
            owner = DOMAIN_TO_PRIMARY_PLATFORM.get(check.domain)
            if owner and owner != card.platform_id:
                rows.append((card.platform_id, owner, f"domain:{check.domain.value}"))
    unique: list[tuple[str, str, str]] = []
    seen: set[tuple[str, str, str]] = set()
    for row in rows:
        if row in seen:
            continue
        seen.add(row)
        unique.append(row)
    return tuple(sorted(unique))


def executive_summary(cards: Sequence[PlatformGovernanceCard]) -> tuple[str, ...]:
    total = len(cards)
    average = round(sum(card.governance_score for card in cards) / total) if total else 0
    blocked = sum(1 for card in cards if card.blockers)
    controlled = sum(1 for card in cards if card.governance_score >= 82 and not card.blockers)
    panel_related = sum(1 for card in cards if any(check.axis == GovernanceAxis.PANEL_BACKEND and check.status == GovernanceStatus.PASS for check in card.checks))
    docs_blockers = sum(1 for card in cards if any(check.domain == GovernanceDomain.DOCS and check.is_blocking for check in card.checks))
    return (
        f"Plataformas avaliadas: {total}",
        f"Score medio de governanca: {average}",
        f"Plataformas bloqueadas por governanca: {blocked}",
        f"Plataformas controladas/institucionais: {controlled}",
        f"Plataformas com backend de painel detectado: {panel_related}",
        f"Checks Docs bloqueantes: {docs_blockers}",
    )


def managerial_summary(cards: Sequence[PlatformGovernanceCard]) -> tuple[str, ...]:
    lines: list[str] = []
    for card in sorted(cards, key=lambda item: (item.governance_score, item.platform_id)):
        blockers = ", ".join(check.title for check in card.blockers[:3]) or "sem blocker"
        action = card.next_actions[0] if card.next_actions else "manter regressao"
        lines.append(
            f"{card.platform_id}: status={card.status_label}; score={card.governance_score}; "
            f"maturidade={card.maturity.value}; blockers={blockers}; acao={action}"
        )
    return tuple(lines)


def blockers_summary(cards: Sequence[PlatformGovernanceCard]) -> tuple[str, ...]:
    blockers: list[str] = []
    for card in cards:
        for check in card.blockers:
            blockers.append(f"{card.platform_id}: {check.title} - {check.next_action}")
    return merge_unique(blockers)[:20]


def dossier_by_platform(round_dossier: ExecutionRoundDossier | None) -> Mapping[str, PlatformOperationalDossier]:
    if round_dossier is None:
        return {}
    return {dossier.platform_id: dossier for dossier in round_dossier.platform_dossiers}


def build_governance_portfolio(
    platform_reports: Sequence[PlatformHumanReport],
    recommendations: Sequence[Recommendation] = (),
    round_dossier: ExecutionRoundDossier | None = None,
    extra_text: Sequence[str] = (),
) -> EcosystemGovernancePortfolio:
    dossiers = dossier_by_platform(round_dossier)
    cards = tuple(
        build_platform_governance_card(
            report,
            recommendations=recommendations,
            dossier=dossiers.get(report.platform.platform_id),
            extra_text=extra_text,
        )
        for report in platform_reports
    )
    candidates = build_order_candidates(cards)
    return EcosystemGovernancePortfolio(
        project_id="tudo-para-ia-mais-humana",
        cards=cards,
        order_candidates=candidates,
        executive_summary=executive_summary(cards),
        managerial_summary=managerial_summary(cards),
        blockers_summary=blockers_summary(cards),
        relation_matrix=relation_matrix(cards),
    )


def governance_portfolio_markdown(portfolio: EcosystemGovernancePortfolio) -> str:
    lines = [
        "# Governanca operacional Mais Humana",
        "",
        f"- project_id: `{portfolio.project_id}`",
        f"- generated_at: `{portfolio.generated_at}`",
        f"- score_medio: `{portfolio.average_governance_score}`",
        "",
        "## Sumario executivo",
        "",
    ]
    lines.extend(f"- {item}" for item in portfolio.executive_summary)
    lines.extend(["", "## Sumario gerencial", ""])
    lines.extend(f"- {item}" for item in portfolio.managerial_summary)
    lines.extend(["", "## Blockers", ""])
    if portfolio.blockers_summary:
        lines.extend(f"- {item}" for item in portfolio.blockers_summary)
    else:
        lines.append("- Nenhum blocker de governanca consolidado.")
    lines.extend(["", "## Plataformas", ""])
    for card in sorted(portfolio.cards, key=lambda item: item.platform_id):
        lines.append(f"### {card.platform_id}")
        lines.append("")
        lines.append(f"- status: `{card.status_label}`")
        lines.append(f"- score_governanca: `{card.governance_score}`")
        lines.append(f"- score_humano: `{card.human_score}`")
        lines.append(f"- maturidade: `{card.maturity.value}`")
        lines.append(f"- blockers: `{len(card.blockers)}`")
        lines.append(f"- warnings: `{len(card.warnings)}`")
        if card.next_actions:
            lines.append(f"- proxima_acao: {card.next_actions[0]}")
        lines.append("")
        lines.append("Checks prioritarios:")
        for check in sorted(card.checks, key=lambda item: (-severity_rank(item.severity), item.score, item.title))[:12]:
            if check.status == GovernanceStatus.NOT_APPLICABLE:
                continue
            lines.append(
                f"- `{check.status.value}` `{check.domain.value}` {check.title}: "
                f"{check.reason} Proxima acao: {check.next_action}"
            )
        lines.append("")
    lines.extend(["", "## Matriz de relacoes", ""])
    for source, target, relation in portfolio.relation_matrix[:120]:
        lines.append(f"- `{source}` -> `{target}` ({relation})")
    return "\n".join(lines).strip() + "\n"


def governance_candidates_markdown(portfolio: EcosystemGovernancePortfolio, limit: int = 20) -> str:
    lines = [
        "# Candidatas de ordem por governanca",
        "",
        "As candidatas abaixo nascem de checks reais de governanca e devem virar OS apenas quando a pendencia nao puder ser resolvida nesta rodada.",
        "",
    ]
    if not portfolio.order_candidates:
        lines.append("- Nenhuma candidata de ordem de governanca.")
        return "\n".join(lines) + "\n"
    for candidate in portfolio.order_candidates[:limit]:
        lines.append(f"## {candidate.candidate_id}")
        lines.append("")
        lines.append(f"- tipo: `{candidate.order_type.value}`")
        lines.append(f"- plataforma: `{candidate.platform_id}`")
        lines.append(f"- prioridade: `{candidate.priority}`")
        lines.append(f"- titulo: {candidate.title}")
        lines.append(f"- motivo: {candidate.reason}")
        lines.append(f"- resultado esperado: {candidate.expected_result}")
        lines.append("- validacoes:")
        for validation in candidate.validations:
            lines.append(f"  - {validation}")
        lines.append("")
    return "\n".join(lines).strip() + "\n"


def governance_cards_rows(portfolio: EcosystemGovernancePortfolio) -> list[list[str]]:
    rows = [["platform", "status", "governance_score", "human_score", "maturity", "blockers", "warnings", "next_action"]]
    for card in sorted(portfolio.cards, key=lambda item: item.platform_id):
        rows.append(
            [
                card.platform_id,
                card.status_label,
                str(card.governance_score),
                str(card.human_score),
                card.maturity.value,
                str(len(card.blockers)),
                str(len(card.warnings)),
                card.next_actions[0] if card.next_actions else "",
            ]
        )
    return rows


def governance_checks_rows(portfolio: EcosystemGovernancePortfolio) -> list[list[str]]:
    rows = [["platform", "check_id", "axis", "domain", "status", "severity", "score", "title", "next_action"]]
    for card in sorted(portfolio.cards, key=lambda item: item.platform_id):
        for check in sorted(card.checks, key=lambda item: (item.domain.value, item.axis.value, item.check_id)):
            if check.status == GovernanceStatus.NOT_APPLICABLE:
                continue
            rows.append(
                [
                    card.platform_id,
                    check.check_id,
                    check.axis.value,
                    check.domain.value,
                    check.status.value,
                    check.severity.value,
                    str(check.score),
                    check.title,
                    check.next_action,
                ]
            )
    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 governance_cards_csv(portfolio: EcosystemGovernancePortfolio) -> str:
    return rows_to_csv(governance_cards_rows(portfolio))


def governance_checks_csv(portfolio: EcosystemGovernancePortfolio) -> str:
    return rows_to_csv(governance_checks_rows(portfolio))


def compact_governance_payload(portfolio: EcosystemGovernancePortfolio) -> dict[str, object]:
    return {
        "project_id": portfolio.project_id,
        "generated_at": portfolio.generated_at,
        "average_governance_score": portfolio.average_governance_score,
        "blocked_platforms": portfolio.blocked_platforms,
        "controlled_platforms": portfolio.controlled_platforms,
        "cards": [
            {
                "platform_id": card.platform_id,
                "status": card.status_label,
                "governance_score": card.governance_score,
                "human_score": card.human_score,
                "maturity": card.maturity.value,
                "blockers": [check.title for check in card.blockers],
                "next_actions": card.next_actions[:5],
            }
            for card in portfolio.cards
        ],
        "order_candidates": [
            {
                "candidate_id": candidate.candidate_id,
                "platform_id": candidate.platform_id,
                "order_type": candidate.order_type.value,
                "title": candidate.title,
                "priority": candidate.priority,
                "source_check_ids": candidate.source_check_ids,
            }
            for candidate in portfolio.order_candidates
        ],
    }


def governance_artifact_records(portfolio: EcosystemGovernancePortfolio, project_root: Path) -> tuple[dict[str, str], ...]:
    records: list[dict[str, str]] = []
    for path, description, function, file_type in (
        ("dados/governanca-operacional.json", "Portfolio completo de governanca operacional.", "governanca", "json"),
        ("dados/governanca-operacional-compacta.json", "Portfolio compacto para leitura rapida.", "governanca compacta", "json"),
        ("ecossistema/GOVERNANCA-OPERACIONAL-MAIS-HUMANA.md", "Relatorio Markdown de governanca operacional.", "governanca", "markdown"),
        ("ecossistema/CANDIDATAS-OS-GOVERNANCA.md", "Candidatas de OS nascidas de checks reais.", "ordens candidatas", "markdown"),
        ("matrizes/governanca-cards.csv", "Resumo tabular dos cards de governanca.", "matriz governanca", "csv"),
        ("matrizes/governanca-checks.csv", "Checks detalhados de governanca.", "matriz governanca", "csv"),
    ):
        records.append(
            {
                "path": str(project_root / path),
                "description": description,
                "function": function,
                "file_type": file_type,
            }
        )
    return tuple(records)


def select_governance_exit_candidates(
    portfolio: EcosystemGovernancePortfolio,
    order_type: OrderType,
    limit: int = 5,
) -> tuple[GovernanceOrderCandidate, ...]:
    selected: list[GovernanceOrderCandidate] = []
    for candidate in portfolio.order_candidates:
        if candidate.order_type == order_type:
            selected.append(candidate)
        if len(selected) >= limit:
            return tuple(selected)
    for candidate in portfolio.order_candidates:
        if candidate not in selected:
            selected.append(candidate)
        if len(selected) >= limit:
            break
    return tuple(selected)


def platform_domain_gap_counts(portfolio: EcosystemGovernancePortfolio) -> dict[str, dict[str, int]]:
    counts: dict[str, dict[str, int]] = {}
    for card in portfolio.cards:
        bucket = counts.setdefault(card.platform_id, {})
        for check in card.checks:
            if check.status in {GovernanceStatus.ATTENTION, GovernanceStatus.FAIL, GovernanceStatus.BLOCKED}:
                bucket[check.domain.value] = bucket.get(check.domain.value, 0) + 1
    return counts


def governance_to_plain_data(portfolio: EcosystemGovernancePortfolio) -> dict[str, object]:
    return as_plain_data(portfolio)
