"""Acceptance scenarios for governance-driven service-order execution."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Iterable, Sequence

from .governance_models import EcosystemGovernancePortfolio, GovernanceStatus
from .models import as_plain_data, merge_unique


@dataclass(slots=True)
class ScenarioAssertion:
    assertion_id: str
    platform_id: str
    expected_terms: tuple[str, ...]
    forbidden_terms: tuple[str, ...]
    explanation: str
    remediation: str

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


@dataclass(slots=True)
class GovernanceScenario:
    scenario_id: str
    title: str
    purpose: str
    owner_domain: str
    assertions: tuple[ScenarioAssertion, ...]
    success_artifacts: tuple[str, ...]
    failure_impact: str

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


@dataclass(slots=True)
class ScenarioAssertionResult:
    assertion_id: str
    platform_id: str
    passed: bool
    missing_terms: tuple[str, ...]
    forbidden_hits: tuple[str, ...]
    explanation: str
    remediation: str

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


@dataclass(slots=True)
class ScenarioEvaluation:
    scenario_id: str
    title: str
    status: str
    score: int
    assertion_results: tuple[ScenarioAssertionResult, ...]
    next_actions: tuple[str, ...]

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


@dataclass(slots=True)
class ScenarioPortfolio:
    scenarios: tuple[GovernanceScenario, ...]
    evaluations: tuple[ScenarioEvaluation, ...]
    summary: tuple[str, ...]

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


def assertion(
    assertion_id: str,
    platform_id: str,
    expected_terms: Iterable[str],
    forbidden_terms: Iterable[str],
    explanation: str,
    remediation: str,
) -> ScenarioAssertion:
    return ScenarioAssertion(
        assertion_id=assertion_id,
        platform_id=platform_id,
        expected_terms=tuple(expected_terms),
        forbidden_terms=tuple(forbidden_terms),
        explanation=explanation,
        remediation=remediation,
    )


SCENARIOS: tuple[GovernanceScenario, ...] = (
    GovernanceScenario(
        "docs-blocker-reconciliation",
        "Docs deixa de ser blocker ambiguo",
        "A rodada deve transformar Docs catalogOnly em leitura minima responseReady ou excecao deliberada.",
        "docs",
        (
            assertion("docs-decision", "docs", ("docs", "catalog", "response", "excecao"), ("ambig",), "Docs precisa de decisao formal.", "registrar decisao Docs"),
            assertion("mcp-readiness", "mcps", ("readiness", "docs", "blocker"), ("global indevido",), "MCP deve refletir a decisao.", "atualizar readiness MCP"),
            assertion("ui-explainable", "ui", ("panel", "same", "source"), ("diverg",), "UI/GPT devem explicar a mesma fonte.", "validar sameSource"),
        ),
        ("decisao Docs", "readiness MCP", "contrato UI/GPT"),
        "Docs segue bloqueando maturidade global sem clareza institucional.",
    ),
    GovernanceScenario(
        "byok-tenant-controlled",
        "BYOK por tenant sem vazamento",
        "Validar BYOK completo com ator, organizacao, entitlement, credentialRef, smoke e audit.",
        "integrations",
        (
            assertion("identity-actor", "identity", ("actor", "organization", "tenant"), ("unknown",), "Ator e tenant precisam existir.", "criar usuario e organizacao"),
            assertion("business-entitlement", "business", ("entitlement", "product", "plan"), ("sem entitlement",), "Uso precisa ser autorizado comercialmente.", "vincular produto"),
            assertion("credential-ref", "integracoes", ("byok", "credentialref", "smoke"), ("token bruto", "secret="), "Credencial deve virar ref segura.", "gerar credentialRef e smoke"),
            assertion("redaction", "compliance", ("redaction", "audit", "trace"), ("password", "api_key"), "Nao vazamento deve ser provado.", "executar redaction check"),
        ),
        ("credentialRef", "smoke readonly", "auditId", "usage"),
        "Autosservico parece pronto, mas falha no primeiro cliente real.",
    ),
    GovernanceScenario(
        "business-sellable-control",
        "Produto vendavel com gate Business",
        "Garantir que produto, plano, quota e bloqueio venham de Business.",
        "business",
        (
            assertion("plan", "business", ("plan", "entitlement", "quota"), ("divergente",), "Plano e quota precisam de fonte unica.", "publicar contrato Business"),
            assertion("billing", "finance", ("invoice", "usage", "reconciliation"), ("sem fatura",), "Finance precisa reconciliar cobranca.", "gerar extrato"),
            assertion("support", "customer_ops", ("support", "diagnostic", "next"), ("erro bruto",), "Suporte precisa explicar bloqueio.", "criar mensagem humana"),
        ),
        ("entitlement", "usage", "invoice", "diagnostic"),
        "Venda e suporte ficam baseados em regras dispersas.",
    ),
    GovernanceScenario(
        "panel-gpt-same-source",
        "Painel e GPT compartilham a mesma fonte",
        "Garantir que screenData, explicacao GPT e evidencia tenham hashes compatíveis.",
        "mcp",
        (
            assertion("screen", "mcps", ("screen", "view", "panel"), ("fonte paralela",), "MCP deve criar instancia de tela.", "criar viewInstance"),
            assertion("hashes", "mcps", ("source", "hash", "records"), ("mismatch",), "Hashes devem existir.", "gerar source hashes"),
            assertion("ui", "ui", ("panelready", "samesource"), ("false",), "UI deve renderizar mesmo contrato.", "validar UI"),
            assertion("docs", "docs", ("contract", "schema", "version"), ("sem versao",), "Docs deve registrar contrato.", "publicar contrato"),
        ),
        ("viewInstance", "sourcePayloadHash", "sourceRecordsHash", "contractVersion"),
        "Painel e GPT podem divergir e quebrar confianca humana.",
    ),
    GovernanceScenario(
        "identity-rbac-negative-path",
        "RBAC com caminho negado provado",
        "Provar tanto allow quanto deny para papel, escopo e tenant.",
        "identity",
        (
            assertion("roles", "identity", ("role", "scope", "permission"), ("allow all",), "Papeis precisam estar mapeados.", "criar matriz RBAC"),
            assertion("deny", "identity", ("deny", "403", "forbidden"), ("sem negacao",), "Negacao precisa ser testada.", "criar teste deny"),
            assertion("audit", "compliance", ("audit", "trace"), ("sem audit",), "Decisao precisa ser auditavel.", "registrar auditId"),
        ),
        ("matriz RBAC", "deny evidence", "auditId"),
        "Permissao fica insegura e suporte nao consegue explicar negacao.",
    ),
    GovernanceScenario(
        "cloudflare-wrangler-not-plugin",
        "Cloudflare opera por wrangler",
        "Tratar plugin como teste inicial e usar wrangler para trabalho real.",
        "cloud",
        (
            assertion("plugin", "integracoes", ("plugin", "cloudflare", "esperada"), ("blocker",), "Plugin deve ser premissa, nao blocker.", "registrar tentativa"),
            assertion("wrangler", "integracoes", ("wrangler", "deploy", "secret"), ("sem wrangler",), "Trabalho real precisa usar wrangler.", "validar wrangler"),
            assertion("runtime", "integracoes", ("route", "binding", "health"), ("missing",), "Runtime precisa ter rotas e bindings.", "validar rota/binding"),
        ),
        ("plugin attempt", "wrangler status", "route health"),
        "Diagnostico Cloudflare fica preso em plugin experimental.",
    ),
    GovernanceScenario(
        "customer-ops-support-cycle",
        "Suporte fecha ciclo de incidente",
        "Atendimento precisa abrir, classificar, encaminhar, resolver e auditar incidente.",
        "support",
        (
            assertion("ticket", "customer_ops", ("ticket", "incident", "open"), ("sem ticket",), "Ticket precisa existir.", "abrir incidente"),
            assertion("handoff", "customer_ops", ("handoff", "owner", "sla"), ("sem owner",), "Responsavel precisa ser definido.", "encaminhar"),
            assertion("resolution", "customer_ops", ("resolved", "evidence"), ("sem fechamento",), "Resolucao precisa ter evidencia.", "fechar incidente"),
            assertion("audit", "compliance", ("audit", "trace"), ("sem audit",), "Ciclo precisa ser auditavel.", "registrar audit"),
        ),
        ("ticket", "owner", "resolution", "audit"),
        "Suporte perde contexto e repete falhas.",
    ),
    GovernanceScenario(
        "release-version-rollback",
        "Release tem versao, regressao e rollback",
        "Cada mudanca precisa de contrato, smoke, rollback e changelog.",
        "governance",
        (
            assertion("version", "platform_base", ("contractversion", "schemaversion"), ("sem versao",), "Versao precisa ser explicita.", "gerar contrato"),
            assertion("smoke", "mcps", ("smoke", "readiness"), ("sem smoke",), "Smoke precisa rodar.", "executar smoke"),
            assertion("rollback", "platform_base", ("rollback", "previous"), ("sem rollback",), "Rollback precisa existir.", "registrar rollback"),
            assertion("docs", "docs", ("changelog", "migration"), ("sem changelog",), "Mudanca precisa ser documentada.", "publicar changelog"),
        ),
        ("contractVersion", "smoke", "rollback", "changelog"),
        "Mudancas futuras quebram contrato sem reversao clara.",
    ),
)


def card_text(portfolio: EcosystemGovernancePortfolio, platform_id: str) -> str:
    card = portfolio.card_for(platform_id)
    if card is None:
        return ""
    parts: list[str] = [card.platform_id, card.status_label, card.maturity.value]
    for check in card.checks:
        parts.extend([check.check_id, check.title, check.status.value, check.reason, check.next_action])
        for evidence in check.evidence:
            parts.extend([evidence.path, evidence.summary])
    return "\n".join(parts).lower()


def evaluate_assertion(assertion_item: ScenarioAssertion, portfolio: EcosystemGovernancePortfolio) -> ScenarioAssertionResult:
    text = card_text(portfolio, assertion_item.platform_id)
    missing = tuple(term for term in assertion_item.expected_terms if term.lower() not in text)
    forbidden = tuple(term for term in assertion_item.forbidden_terms if term.lower() in text)
    card = portfolio.card_for(assertion_item.platform_id)
    blocked = card is None or (card.blockers and any(check.status == GovernanceStatus.BLOCKED for check in card.blockers))
    passed = not missing and not forbidden and not blocked
    return ScenarioAssertionResult(
        assertion_id=assertion_item.assertion_id,
        platform_id=assertion_item.platform_id,
        passed=passed,
        missing_terms=missing,
        forbidden_hits=forbidden,
        explanation=assertion_item.explanation,
        remediation=assertion_item.remediation,
    )


def evaluate_scenario(scenario: GovernanceScenario, portfolio: EcosystemGovernancePortfolio) -> ScenarioEvaluation:
    results = tuple(evaluate_assertion(assertion_item, portfolio) for assertion_item in scenario.assertions)
    passed = sum(1 for item in results if item.passed)
    total = len(results)
    score = round((passed / total) * 100) if total else 0
    status = "pronto" if score >= 90 else "util" if score >= 70 else "atencao" if score >= 40 else "bloqueado"
    next_actions = merge_unique(item.remediation for item in results if not item.passed)
    return ScenarioEvaluation(
        scenario_id=scenario.scenario_id,
        title=scenario.title,
        status=status,
        score=score,
        assertion_results=results,
        next_actions=next_actions,
    )


def build_scenario_portfolio(portfolio: EcosystemGovernancePortfolio, scenarios: Sequence[GovernanceScenario] = SCENARIOS) -> ScenarioPortfolio:
    evaluations = tuple(evaluate_scenario(scenario, portfolio) for scenario in scenarios)
    avg = round(sum(item.score for item in evaluations) / len(evaluations)) if evaluations else 0
    blocked = sum(1 for item in evaluations if item.status == "bloqueado")
    ready = sum(1 for item in evaluations if item.status in {"pronto", "util"})
    summary = (
        f"Cenarios avaliados: {len(evaluations)}",
        f"Cenarios prontos/uteis: {ready}",
        f"Cenarios bloqueados: {blocked}",
        f"Score medio de cenario: {avg}",
    )
    return ScenarioPortfolio(scenarios=tuple(scenarios), evaluations=evaluations, summary=summary)


def scenarios_markdown(portfolio: ScenarioPortfolio) -> str:
    by_id = {scenario.scenario_id: scenario for scenario in portfolio.scenarios}
    lines = ["# Cenarios de aceite de governanca", ""]
    lines.extend(f"- {item}" for item in portfolio.summary)
    lines.append("")
    for evaluation in sorted(portfolio.evaluations, key=lambda item: (item.score, item.scenario_id)):
        scenario = by_id[evaluation.scenario_id]
        lines.append(f"## {scenario.title}")
        lines.append("")
        lines.append(f"- scenario_id: `{scenario.scenario_id}`")
        lines.append(f"- dominio: `{scenario.owner_domain}`")
        lines.append(f"- status: `{evaluation.status}`")
        lines.append(f"- score: `{evaluation.score}`")
        lines.append(f"- impacto se falhar: {scenario.failure_impact}")
        if evaluation.next_actions:
            lines.append("- proximas acoes:")
            for action in evaluation.next_actions:
                lines.append(f"  - {action}")
        lines.append("- assertions:")
        for result in evaluation.assertion_results:
            status = "ok" if result.passed else "falha"
            details = []
            if result.missing_terms:
                details.append("faltam " + ", ".join(result.missing_terms))
            if result.forbidden_hits:
                details.append("proibidos " + ", ".join(result.forbidden_hits))
            detail_text = "; ".join(details) if details else result.explanation
            lines.append(f"  - `{status}` `{result.platform_id}.{result.assertion_id}`: {detail_text}")
        lines.append("")
    return "\n".join(lines).strip() + "\n"


def scenario_rows(portfolio: ScenarioPortfolio) -> list[list[str]]:
    rows = [["scenario_id", "status", "score", "assertion_id", "platform", "passed", "missing", "forbidden", "remediation"]]
    for evaluation in portfolio.evaluations:
        for result in evaluation.assertion_results:
            rows.append(
                [
                    evaluation.scenario_id,
                    evaluation.status,
                    str(evaluation.score),
                    result.assertion_id,
                    result.platform_id,
                    "yes" if result.passed else "no",
                    " | ".join(result.missing_terms),
                    " | ".join(result.forbidden_hits),
                    result.remediation,
                ]
            )
    return rows


def scenario_action_items(portfolio: ScenarioPortfolio, limit: int = 25) -> tuple[str, ...]:
    actions: list[str] = []
    for evaluation in sorted(portfolio.evaluations, key=lambda item: (item.score, item.scenario_id)):
        for action in evaluation.next_actions:
            actions.append(f"{evaluation.scenario_id}: {action}")
            if len(actions) >= limit:
                return merge_unique(actions)
    return merge_unique(actions)
