"""Typed governance models for the Mais Humana operational layer.

The platform already scans repositories and builds human-facing reports.  This
module adds a more operational vocabulary around that data: domains, axes,
check templates, check results, order lifecycle records, and round closeout
packages.  Keeping these records explicit makes it possible to explain why an
order was closed, why a blocker remains real, and which next order is justified
by local evidence.
"""

from __future__ import annotations

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

from .models import OrderType, as_plain_data, merge_unique, slugify, utc_now


class GovernanceDomain(str, Enum):
    """Cross-platform operational domains tracked by Mais Humana."""

    REPOSITORY = "repository"
    DOCUMENTATION = "documentation"
    CONTRACT = "contract"
    TESTS = "tests"
    RUNTIME = "runtime"
    MCP = "mcp"
    IDENTITY = "identity"
    BUSINESS = "business"
    DOCS = "docs"
    INTEGRATIONS = "integrations"
    COMPLIANCE = "compliance"
    FINANCE = "finance"
    CUSTOMER_OPS = "customer_ops"
    UI = "ui"
    CLOUD = "cloud"
    SECURITY = "security"
    OBSERVABILITY = "observability"
    SUPPORT = "support"
    GOVERNANCE = "governance"
    HUMAN_EXPERIENCE = "human_experience"


class GovernanceAxis(str, Enum):
    """A specific axis used to decide human-operational maturity."""

    LOCAL_TRACEABILITY = "local_traceability"
    HUMAN_DOCUMENTATION = "human_documentation"
    CONTRACT_VERSIONING = "contract_versioning"
    TESTABLE_BEHAVIOR = "testable_behavior"
    PANEL_BACKEND = "panel_backend"
    GPT_PANEL_EQUIVALENCE = "gpt_panel_equivalence"
    IDENTITY_SCOPE = "identity_scope"
    BUSINESS_GATE = "business_gate"
    DOCS_CANONICALITY = "docs_canonicality"
    BYOK_CREDENTIALS = "byok_credentials"
    SECRET_REDACTION = "secret_redaction"
    SUPPORT_DIAGNOSTICS = "support_diagnostics"
    INCIDENT_LIFECYCLE = "incident_lifecycle"
    AUDIT_EVIDENCE = "audit_evidence"
    READINESS_HEALTH = "readiness_health"
    CLOUD_OPERATION = "cloud_operation"
    DATA_TRUTH = "data_truth"
    RELEASE_GOVERNANCE = "release_governance"
    COMMERCIAL_MATURITY = "commercial_maturity"
    HUMAN_VALUE = "human_value"


class GovernanceStatus(str, Enum):
    """Normalized result for a governance check."""

    PASS = "pass"
    ATTENTION = "attention"
    FAIL = "fail"
    BLOCKED = "blocked"
    EXCEPTION = "exception"
    NOT_APPLICABLE = "not_applicable"


class GovernanceSeverity(str, Enum):
    """Severity assigned to a failed or partial check."""

    INFO = "info"
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


class GovernanceEvidenceKind(str, Enum):
    """Kind of evidence attached to a governance result."""

    CODE = "code"
    DOCUMENTATION = "documentation"
    CONTRACT = "contract"
    TEST = "test"
    CONFIG = "config"
    REPORT = "report"
    ORDER = "order"
    SQL = "sql"
    RUNTIME = "runtime"
    ABSENCE = "absence"
    DERIVED = "derived"


class GovernanceMaturity(str, Enum):
    """Governance maturity from a long-running project perspective."""

    ABSENT = "absent"
    FOUNDATIONAL = "foundational"
    LOCAL_READY = "local_ready"
    EXPLAINABLE = "explainable"
    PANEL_READY = "panel_ready"
    CONTROLLED = "controlled"
    INSTITUTIONAL = "institutional"


class OrderLifecycleStatus(str, Enum):
    """Lifecycle status used for central service-order files."""

    PLANNED = "planejada"
    RUNNING = "em_execucao"
    COMPLETED = "concluida"
    PARTIAL = "parcial"
    BLOCKED = "bloqueada"
    SUPERSEDED = "substituida"
    UNKNOWN = "desconhecida"


class RoundMinimumStatus(str, Enum):
    """Whether a round minimum was met."""

    MET = "met"
    PARTIAL = "partial"
    IMPOSSIBLE = "impossible"


SEVERITY_WEIGHT: dict[GovernanceSeverity, int] = {
    GovernanceSeverity.INFO: 0,
    GovernanceSeverity.LOW: 1,
    GovernanceSeverity.MEDIUM: 3,
    GovernanceSeverity.HIGH: 6,
    GovernanceSeverity.CRITICAL: 10,
}


STATUS_SCORE: dict[GovernanceStatus, int] = {
    GovernanceStatus.PASS: 100,
    GovernanceStatus.EXCEPTION: 82,
    GovernanceStatus.ATTENTION: 55,
    GovernanceStatus.FAIL: 25,
    GovernanceStatus.BLOCKED: 0,
    GovernanceStatus.NOT_APPLICABLE: 100,
}


MATURITY_SCORE: dict[GovernanceMaturity, int] = {
    GovernanceMaturity.ABSENT: 0,
    GovernanceMaturity.FOUNDATIONAL: 20,
    GovernanceMaturity.LOCAL_READY: 42,
    GovernanceMaturity.EXPLAINABLE: 62,
    GovernanceMaturity.PANEL_READY: 76,
    GovernanceMaturity.CONTROLLED: 88,
    GovernanceMaturity.INSTITUTIONAL: 100,
}


def severity_rank(value: GovernanceSeverity) -> int:
    return SEVERITY_WEIGHT.get(value, 0)


def status_score(value: GovernanceStatus) -> int:
    return STATUS_SCORE.get(value, 0)


def maturity_score(value: GovernanceMaturity) -> int:
    return MATURITY_SCORE.get(value, 0)


def score_to_maturity(score: int) -> GovernanceMaturity:
    score = max(0, min(100, int(score)))
    if score >= 92:
        return GovernanceMaturity.INSTITUTIONAL
    if score >= 82:
        return GovernanceMaturity.CONTROLLED
    if score >= 72:
        return GovernanceMaturity.PANEL_READY
    if score >= 58:
        return GovernanceMaturity.EXPLAINABLE
    if score >= 36:
        return GovernanceMaturity.LOCAL_READY
    if score >= 12:
        return GovernanceMaturity.FOUNDATIONAL
    return GovernanceMaturity.ABSENT


def worst_severity(values: Iterable[GovernanceSeverity]) -> GovernanceSeverity:
    ordered = sorted(values, key=severity_rank, reverse=True)
    return ordered[0] if ordered else GovernanceSeverity.INFO


def normalize_path(path: str | Path) -> str:
    return str(path).replace("\\", "/")


@dataclass(slots=True)
class GovernanceEvidence:
    """Evidence reference used by governance checks."""

    path: str
    summary: str
    kind: GovernanceEvidenceKind = GovernanceEvidenceKind.DERIVED
    line: int | None = None
    confidence: float = 0.5
    excerpt_hash: str = ""

    @property
    def reference(self) -> str:
        path = normalize_path(self.path)
        if self.line is None:
            return path
        return f"{path}:{self.line}"

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


@dataclass(slots=True)
class CheckTemplate:
    """Declarative governance check evaluated against a platform report."""

    check_id: str
    axis: GovernanceAxis
    domain: GovernanceDomain
    title: str
    purpose: str
    positive_terms: tuple[str, ...]
    negative_terms: tuple[str, ...]
    required_terms: tuple[str, ...]
    evidence_hints: tuple[str, ...]
    applies_to: tuple[str, ...]
    not_applicable_reason: str
    pass_summary: str
    attention_summary: str
    fail_summary: str
    suggested_action: str
    validation_steps: tuple[str, ...]
    severity: GovernanceSeverity = GovernanceSeverity.MEDIUM
    required_for_human_ready: bool = False
    creates_order_type: OrderType = OrderType.MANAGERIAL
    order_title: str = ""
    order_priority: str = "media"

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

    @property
    def normalized_order_title(self) -> str:
        return self.order_title or self.title

    def applies_to_platform(self, platform_id: str) -> bool:
        return not self.applies_to or platform_id in self.applies_to


@dataclass(slots=True)
class GovernanceCheckResult:
    """Evaluated result for one governance check."""

    check_id: str
    platform_id: str
    axis: GovernanceAxis
    domain: GovernanceDomain
    title: str
    status: GovernanceStatus
    severity: GovernanceSeverity
    maturity: GovernanceMaturity
    score: int
    reason: str
    next_action: str
    evidence: tuple[GovernanceEvidence, ...]
    validation_steps: tuple[str, ...]
    order_title: str
    order_type: OrderType
    order_priority: str
    required_for_human_ready: bool
    generated_at: str = field(default_factory=utc_now)

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

    @property
    def is_blocking(self) -> bool:
        return self.status == GovernanceStatus.BLOCKED or (
            self.required_for_human_ready
            and self.status in {GovernanceStatus.FAIL, GovernanceStatus.BLOCKED}
        )

    @property
    def needs_order(self) -> bool:
        return self.status in {GovernanceStatus.ATTENTION, GovernanceStatus.FAIL, GovernanceStatus.BLOCKED}

    @property
    def compact_status(self) -> str:
        return f"{self.status.value}/{self.severity.value}/{self.score}"


@dataclass(slots=True)
class PlatformGovernanceCard:
    """Governance rollup for one platform."""

    platform_id: str
    title: str
    repo_path: str
    checks: tuple[GovernanceCheckResult, ...]
    human_score: int
    governance_score: int
    maturity: GovernanceMaturity
    blockers: tuple[GovernanceCheckResult, ...]
    warnings: tuple[GovernanceCheckResult, ...]
    strengths: tuple[str, ...]
    next_actions: tuple[str, ...]
    relation_summary: tuple[str, ...]
    generated_at: str = field(default_factory=utc_now)

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

    @property
    def ready_for_human_round(self) -> bool:
        return self.governance_score >= 72 and not self.blockers

    @property
    def status_label(self) -> str:
        if any(item.severity == GovernanceSeverity.CRITICAL for item in self.blockers):
            return "critico"
        if self.blockers:
            return "bloqueado"
        if self.governance_score >= 82:
            return "controlado"
        if self.governance_score >= 62:
            return "explicavel"
        if self.governance_score >= 42:
            return "local"
        return "fundacao"

    def checks_by_domain(self) -> dict[str, list[GovernanceCheckResult]]:
        grouped: dict[str, list[GovernanceCheckResult]] = {}
        for check in self.checks:
            grouped.setdefault(check.domain.value, []).append(check)
        for checks in grouped.values():
            checks.sort(key=lambda item: (-severity_rank(item.severity), item.axis.value, item.title))
        return grouped


@dataclass(slots=True)
class GovernanceOrderCandidate:
    """Candidate order created from failed or partial governance checks."""

    candidate_id: str
    platform_id: str
    order_type: OrderType
    title: str
    purpose: str
    reason: str
    expected_result: str
    priority: str
    affected_paths: tuple[str, ...]
    validations: tuple[str, ...]
    source_check_ids: tuple[str, ...]
    status: OrderLifecycleStatus = OrderLifecycleStatus.PLANNED
    generated_at: str = field(default_factory=utc_now)

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

    @property
    def slug(self) -> str:
        return slugify(self.title)


@dataclass(slots=True)
class EcosystemGovernancePortfolio:
    """Governance view for the whole ecosystem."""

    project_id: str
    cards: tuple[PlatformGovernanceCard, ...]
    order_candidates: tuple[GovernanceOrderCandidate, ...]
    executive_summary: tuple[str, ...]
    managerial_summary: tuple[str, ...]
    blockers_summary: tuple[str, ...]
    relation_matrix: tuple[tuple[str, str, str], ...]
    generated_at: str = field(default_factory=utc_now)

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

    @property
    def average_governance_score(self) -> int:
        if not self.cards:
            return 0
        return round(sum(card.governance_score for card in self.cards) / len(self.cards))

    @property
    def blocked_platforms(self) -> tuple[str, ...]:
        return tuple(card.platform_id for card in self.cards if card.blockers)

    @property
    def controlled_platforms(self) -> tuple[str, ...]:
        return tuple(card.platform_id for card in self.cards if card.governance_score >= 82 and not card.blockers)

    def card_for(self, platform_id: str) -> PlatformGovernanceCard | None:
        for card in self.cards:
            if card.platform_id == platform_id:
                return card
        return None


@dataclass(slots=True)
class ParsedServiceOrder:
    """Parsed service-order file from the central platform folder."""

    path: str
    order_id: str
    order_type: OrderType
    project_id: str
    status: OrderLifecycleStatus
    priority: str
    title: str
    platform_hint: str
    purpose: str
    object_scope: str
    reason: str
    expected_result: str
    affected_paths: tuple[str, ...]
    validations: tuple[str, ...]
    raw_headings: Mapping[str, str]
    parsed_at: str = field(default_factory=utc_now)

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

    @property
    def is_active(self) -> bool:
        return self.status in {
            OrderLifecycleStatus.PLANNED,
            OrderLifecycleStatus.RUNNING,
            OrderLifecycleStatus.PARTIAL,
            OrderLifecycleStatus.UNKNOWN,
        }

    @property
    def short_name(self) -> str:
        if "__" in self.order_id:
            return self.order_id.split("__", 1)[1].replace("-", " ")
        return self.title or self.order_id


@dataclass(slots=True)
class OrderLifecycleDecision:
    """Decision produced for one parsed order in the current round."""

    order: ParsedServiceOrder
    final_status: OrderLifecycleStatus
    platform_id: str
    reason: str
    evidence: tuple[GovernanceEvidence, ...]
    pending_items: tuple[str, ...]
    resulting_candidates: tuple[str, ...]
    validation_steps: tuple[str, ...]
    generated_at: str = field(default_factory=utc_now)

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

    @property
    def has_pending(self) -> bool:
        return bool(self.pending_items)

    @property
    def compact_line(self) -> str:
        return f"{self.order.order_id}|{self.final_status.value}|{self.platform_id}|{len(self.pending_items)}"


@dataclass(slots=True)
class RoundMinimum:
    """One minimum required by the service-order protocol."""

    minimum_id: str
    title: str
    required_value: int
    actual_value: int
    status: RoundMinimumStatus
    reason: str
    next_action: str

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


@dataclass(slots=True)
class RoundExecutionPackage:
    """Full operational package for this service-order round."""

    round_id: str
    project_id: str
    parsed_orders: tuple[ParsedServiceOrder, ...]
    decisions: tuple[OrderLifecycleDecision, ...]
    minimums: tuple[RoundMinimum, ...]
    output_candidates: tuple[GovernanceOrderCandidate, ...]
    active_after_round: tuple[str, ...]
    completed_count: int
    partial_count: int
    blocked_count: int
    generated_at: str = field(default_factory=utc_now)

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

    @property
    def pending_items(self) -> tuple[str, ...]:
        items: list[str] = []
        for decision in self.decisions:
            for pending in decision.pending_items:
                items.append(f"{decision.order.order_id}: {pending}")
        for minimum in self.minimums:
            if minimum.status != RoundMinimumStatus.MET:
                items.append(f"{minimum.title}: {minimum.reason} Proxima acao: {minimum.next_action}")
        return merge_unique(items)

    @property
    def success_label(self) -> str:
        if self.blocked_count:
            return "parcial-com-bloqueios"
        if self.partial_count:
            return "parcial"
        return "concluida"


def evidence_from_path(path: str | Path, summary: str, *, kind: GovernanceEvidenceKind = GovernanceEvidenceKind.DERIVED) -> GovernanceEvidence:
    return GovernanceEvidence(path=normalize_path(path), summary=summary, kind=kind, confidence=0.65)


def minimum_status(actual: int, required: int, impossible: bool = False) -> RoundMinimumStatus:
    if actual >= required:
        return RoundMinimumStatus.MET
    if impossible:
        return RoundMinimumStatus.IMPOSSIBLE
    return RoundMinimumStatus.PARTIAL


def dedupe_candidates(candidates: Iterable[GovernanceOrderCandidate], limit: int | None = None) -> tuple[GovernanceOrderCandidate, ...]:
    seen: set[tuple[str, str, str]] = set()
    output: list[GovernanceOrderCandidate] = []
    for candidate in sorted(candidates, key=lambda item: (item.order_type.value, item.priority, item.platform_id, item.title)):
        key = (candidate.platform_id, candidate.order_type.value, candidate.slug)
        if key in seen:
            continue
        seen.add(key)
        output.append(candidate)
        if limit is not None and len(output) >= limit:
            break
    return tuple(output)
