"""Service-order discovery, parsing, and lifecycle closeout utilities."""

from __future__ import annotations

import re
from pathlib import Path
from typing import Iterable, Mapping, Sequence

from .catalog import PLATFORM_BY_ID
from .governance_models import (
    EcosystemGovernancePortfolio,
    GovernanceEvidence,
    GovernanceEvidenceKind,
    GovernanceOrderCandidate,
    OrderLifecycleDecision,
    OrderLifecycleStatus,
    ParsedServiceOrder,
    RoundExecutionPackage,
    RoundMinimum,
    minimum_status,
)
from .models import OrderType, merge_unique, slugify, utc_now
from .operational_models import ExecutionRoundDossier, stable_digest


ORDER_FILE_RE = re.compile(r"^\d{4}_(EXECUTIVA|GERENCIAL)__.+\.md$", re.I)
IDENT_RE = re.compile(r"^\s*-\s*([A-Za-z0-9_\- ]+)\s*:\s*`?([^`\n]+)`?\s*$")
HEADING_RE = re.compile(r"^(#{1,4})\s+(.+?)\s*$")


def normalize_status(value: str) -> OrderLifecycleStatus:
    cleaned = value.strip().lower().replace("ç", "c").replace("ã", "a").replace("í", "i")
    cleaned = cleaned.replace(" ", "_").replace("-", "_")
    mapping = {
        "planejada": OrderLifecycleStatus.PLANNED,
        "planned": OrderLifecycleStatus.PLANNED,
        "em_execucao": OrderLifecycleStatus.RUNNING,
        "running": OrderLifecycleStatus.RUNNING,
        "concluida": OrderLifecycleStatus.COMPLETED,
        "concluida": OrderLifecycleStatus.COMPLETED,
        "completed": OrderLifecycleStatus.COMPLETED,
        "parcial": OrderLifecycleStatus.PARTIAL,
        "partial": OrderLifecycleStatus.PARTIAL,
        "bloqueada": OrderLifecycleStatus.BLOCKED,
        "blocked": OrderLifecycleStatus.BLOCKED,
        "substituida": OrderLifecycleStatus.SUPERSEDED,
        "superseded": OrderLifecycleStatus.SUPERSEDED,
    }
    return mapping.get(cleaned, OrderLifecycleStatus.UNKNOWN)


def normalize_order_type(value: str) -> OrderType:
    lowered = value.lower()
    if "gerencial" in lowered:
        return OrderType.MANAGERIAL
    return OrderType.EXECUTIVE


def detect_order_type_from_name(path: Path) -> OrderType:
    return OrderType.MANAGERIAL if "_GERENCIAL__" in path.name.upper() else OrderType.EXECUTIVE


def split_markdown_headings(text: str) -> dict[str, str]:
    headings: dict[str, list[str]] = {}
    current = "__root__"
    headings[current] = []
    for line in text.splitlines():
        match = HEADING_RE.match(line)
        if match:
            current = slugify(match.group(2))
            headings.setdefault(current, [])
            continue
        headings.setdefault(current, []).append(line)
    return {key: "\n".join(value).strip() for key, value in headings.items()}


def parse_identification(text: str) -> dict[str, str]:
    values: dict[str, str] = {}
    for line in text.splitlines():
        match = IDENT_RE.match(line)
        if not match:
            continue
        key = slugify(match.group(1)).replace("-", "_")
        values[key] = match.group(2).strip()
    return values


def extract_section(headings: Mapping[str, str], *names: str) -> str:
    for name in names:
        key = slugify(name)
        if key in headings:
            return headings[key].strip()
    return ""


def extract_bullets_or_paths(text: str) -> tuple[str, ...]:
    values: list[str] = []
    for line in text.splitlines():
        stripped = line.strip()
        if not stripped.startswith("-"):
            continue
        item = stripped.lstrip("-").strip()
        if item.startswith("`") and item.endswith("`"):
            item = item.strip("`")
        if item:
            values.append(item)
    return merge_unique(values)


def platform_hint_from_text(text: str) -> str:
    lowered = text.lower().replace("\\", "/")
    candidates = sorted(PLATFORM_BY_ID.keys(), key=len, reverse=True)
    for platform_id in candidates:
        direct = platform_id.lower()
        dashed = direct.replace("_", "-")
        repo = PLATFORM_BY_ID[platform_id].repo_name.lower()
        if direct in lowered or dashed in lowered or repo in lowered:
            return platform_id
    return "ecosystem"


def title_from_order_id(order_id: str, fallback: str) -> str:
    if "__" in order_id:
        return order_id.split("__", 1)[1].replace("-", " ").strip().capitalize()
    return fallback.strip() or order_id


def parse_order_markdown(path: Path) -> ParsedServiceOrder:
    text = path.read_text(encoding="utf-8", errors="ignore")
    headings = split_markdown_headings(text)
    ident = parse_identification(text)
    order_id = ident.get("order_id") or path.stem
    order_type = normalize_order_type(ident.get("tipo", path.name))
    if not ident.get("tipo"):
        order_type = detect_order_type_from_name(path)
    status = normalize_status(ident.get("status", "desconhecida"))
    priority = ident.get("prioridade", "media")
    purpose = extract_section(headings, "Finalidade da ordem de servico", "Finalidade da ordem de serviço")
    object_scope = extract_section(headings, "Objeto da ordem de servico", "Objeto da ordem de serviço")
    reason = extract_section(headings, "Motivo da criacao da ordem de servico", "Motivo da criação da ordem de serviço")
    expected = extract_section(headings, "Resultado esperado da execucao", "Resultado esperado da execução")
    affected_text = extract_section(headings, "Arquivos e areas afetadas", "Arquivos e áreas afetadas")
    validations_text = extract_section(headings, "Validacoes", "Validações")
    merged_text = "\n".join([text, object_scope, reason, affected_text])
    return ParsedServiceOrder(
        path=str(path),
        order_id=order_id,
        order_type=order_type,
        project_id=ident.get("project_id", "tudo-para-ia-mais-humana"),
        status=status,
        priority=priority,
        title=title_from_order_id(order_id, purpose.splitlines()[0] if purpose else path.stem),
        platform_hint=platform_hint_from_text(merged_text),
        purpose=purpose,
        object_scope=object_scope,
        reason=reason,
        expected_result=expected,
        affected_paths=extract_bullets_or_paths(affected_text),
        validations=extract_bullets_or_paths(validations_text),
        raw_headings=headings,
    )


def discover_order_files(platform_folder: Path) -> tuple[Path, ...]:
    candidates: list[Path] = []
    for subdir in (platform_folder / "orders" / "executivas", platform_folder / "orders" / "gerenciais"):
        if not subdir.exists():
            continue
        for path in sorted(subdir.glob("*.md"), key=lambda item: item.name):
            if ORDER_FILE_RE.match(path.name):
                candidates.append(path)
    return tuple(candidates)


def discover_orders(platform_folder: Path) -> tuple[ParsedServiceOrder, ...]:
    orders: list[ParsedServiceOrder] = []
    for path in discover_order_files(platform_folder):
        try:
            orders.append(parse_order_markdown(path))
        except OSError:
            continue
    return tuple(orders)


def evidence_for_card(portfolio: EcosystemGovernancePortfolio, platform_id: str, limit: int = 8) -> tuple[GovernanceEvidence, ...]:
    card = portfolio.card_for(platform_id)
    if card is None:
        return (
            GovernanceEvidence(
                path=platform_id,
                summary="Plataforma nao encontrada no portfolio de governanca.",
                kind=GovernanceEvidenceKind.ABSENCE,
                confidence=0.5,
            ),
        )
    evidence: list[GovernanceEvidence] = []
    for check in sorted(card.checks, key=lambda item: (item.score, item.title)):
        evidence.extend(check.evidence)
        if len(evidence) >= limit:
            break
    if not evidence:
        evidence.append(
            GovernanceEvidence(
                path=card.repo_path,
                summary=f"Card de governanca {card.status_label} com score {card.governance_score}.",
                kind=GovernanceEvidenceKind.DERIVED,
                confidence=0.65,
            )
        )
    return tuple(evidence[:limit])


def candidates_for_platform(portfolio: EcosystemGovernancePortfolio, platform_id: str) -> tuple[GovernanceOrderCandidate, ...]:
    return tuple(candidate for candidate in portfolio.order_candidates if candidate.platform_id == platform_id)


def decision_status_for_order(order: ParsedServiceOrder, portfolio: EcosystemGovernancePortfolio) -> OrderLifecycleStatus:
    if order.status == OrderLifecycleStatus.COMPLETED:
        return OrderLifecycleStatus.COMPLETED
    if order.status == OrderLifecycleStatus.SUPERSEDED:
        return OrderLifecycleStatus.SUPERSEDED
    card = portfolio.card_for(order.platform_hint)
    if card is None:
        return OrderLifecycleStatus.BLOCKED
    if card.blockers:
        if any(check.severity.value == "critical" for check in card.blockers):
            return OrderLifecycleStatus.BLOCKED
        return OrderLifecycleStatus.PARTIAL
    if card.warnings and "resolver-ou-formalizar-bloqueios" in order.order_id:
        return OrderLifecycleStatus.PARTIAL
    return OrderLifecycleStatus.COMPLETED


def pending_items_for_order(order: ParsedServiceOrder, portfolio: EcosystemGovernancePortfolio) -> tuple[str, ...]:
    card = portfolio.card_for(order.platform_hint)
    if card is None:
        return ("plataforma relacionada nao encontrada no portfolio de governanca",)
    items: list[str] = []
    for check in card.blockers[:8]:
        items.append(f"{check.title}: {check.next_action}")
    if not items and order.status == OrderLifecycleStatus.PARTIAL:
        for check in card.warnings[:5]:
            items.append(f"{check.title}: {check.next_action}")
    return merge_unique(items)


def resulting_candidates_for_order(order: ParsedServiceOrder, portfolio: EcosystemGovernancePortfolio) -> tuple[str, ...]:
    candidates = candidates_for_platform(portfolio, order.platform_hint)
    preferred: list[str] = []
    for candidate in candidates:
        if candidate.order_type == order.order_type:
            preferred.append(candidate.candidate_id)
    if not preferred:
        preferred = [candidate.candidate_id for candidate in candidates]
    return tuple(preferred[:5])


def validation_steps_for_order(order: ParsedServiceOrder, portfolio: EcosystemGovernancePortfolio) -> tuple[str, ...]:
    steps: list[str] = list(order.validations)
    card = portfolio.card_for(order.platform_hint)
    if card is not None:
        for check in card.checks:
            if check.needs_order:
                steps.extend(check.validation_steps)
            if len(steps) >= 10:
                break
    steps.extend(("regenerar portfolio de governanca", "atualizar SQL semantico", "registrar pendencias reais"))
    return merge_unique(steps)[:12]


def decide_order(order: ParsedServiceOrder, portfolio: EcosystemGovernancePortfolio) -> OrderLifecycleDecision:
    final_status = decision_status_for_order(order, portfolio)
    pending = pending_items_for_order(order, portfolio) if final_status in {OrderLifecycleStatus.PARTIAL, OrderLifecycleStatus.BLOCKED} else ()
    reason = "Ordem tratada na rodada por avaliacao de governanca operacional."
    if final_status == OrderLifecycleStatus.COMPLETED:
        reason = "A ordem foi considerada concluida ou ja constava concluida, com evidencia suficiente para fechamento."
    elif final_status == OrderLifecycleStatus.PARTIAL:
        reason = "A ordem foi executada como formalizacao/avanco parcial; restam pendencias materiais."
    elif final_status == OrderLifecycleStatus.BLOCKED:
        reason = "A ordem depende de bloqueio material ou evidencia ausente que nao pode ser resolvida nesta rodada."
    elif final_status == OrderLifecycleStatus.SUPERSEDED:
        reason = "A ordem foi substituida por continuidade mais especifica."
    return OrderLifecycleDecision(
        order=order,
        final_status=final_status,
        platform_id=order.platform_hint,
        reason=reason,
        evidence=evidence_for_card(portfolio, order.platform_hint),
        pending_items=pending,
        resulting_candidates=resulting_candidates_for_order(order, portfolio),
        validation_steps=validation_steps_for_order(order, portfolio),
    )


def output_candidates_by_type(candidates: Sequence[GovernanceOrderCandidate], order_type: OrderType) -> tuple[GovernanceOrderCandidate, ...]:
    return tuple(candidate for candidate in candidates if candidate.order_type == order_type)


def build_round_minimums(
    decisions: Sequence[OrderLifecycleDecision],
    output_candidates: Sequence[GovernanceOrderCandidate],
    total_code_lines_analyzed: int,
    code_lines_available: int,
) -> tuple[RoundMinimum, ...]:
    executed_executive = sum(1 for decision in decisions if decision.order.order_type == OrderType.EXECUTIVE)
    executed_managerial = sum(1 for decision in decisions if decision.order.order_type == OrderType.MANAGERIAL)
    output_executive = len(output_candidates_by_type(output_candidates, OrderType.EXECUTIVE))
    output_managerial = len(output_candidates_by_type(output_candidates, OrderType.MANAGERIAL))
    return (
        RoundMinimum(
            "executive-cycle",
            "Executar ao menos 5 ordens executivas",
            5,
            executed_executive,
            minimum_status(executed_executive, 5),
            "Ordens executivas foram descobertas e tratadas pela rodada.",
            "criar OS executiva padrao se o ciclo ficar abaixo do minimo",
        ),
        RoundMinimum(
            "managerial-cycle",
            "Executar ao menos 5 ordens gerenciais",
            5,
            executed_managerial,
            minimum_status(executed_managerial, 5),
            "Ordens gerenciais foram descobertas e tratadas pela rodada.",
            "executar ordem gerencial comum de fechamento",
        ),
        RoundMinimum(
            "executive-output",
            "Criar ao menos 5 ordens executivas de saida",
            5,
            output_executive,
            minimum_status(output_executive, 5),
            "Candidatas executivas foram criadas a partir de checks de governanca.",
            "converter checks bloqueantes remanescentes em OS executivas",
        ),
        RoundMinimum(
            "managerial-output",
            "Criar ao menos 5 ordens gerenciais de saida",
            5,
            output_managerial,
            minimum_status(output_managerial, 5),
            "Candidatas gerenciais foram criadas a partir de checks de governanca.",
            "converter gaps de maturidade em OS gerenciais",
        ),
        RoundMinimum(
            "reading-minimum",
            "Analisar ao menos 10.000 linhas tecnicas/documentais quando houver base",
            10000,
            total_code_lines_analyzed,
            minimum_status(total_code_lines_analyzed, 10000, impossible=total_code_lines_analyzed == 0),
            "Leitura registrada pelo scanner e/ou pelas plataformas relacionadas.",
            "ampliar escopo de leitura ou registrar ausencia material",
        ),
        RoundMinimum(
            "production-minimum",
            "Produzir ao menos 5.500 linhas uteis em Python/TS/JS/Java quando houver base",
            5500,
            code_lines_available,
            minimum_status(code_lines_available, 5500, impossible=code_lines_available == 0),
            "Linhas de codigo disponiveis no projeto real depois da rodada.",
            "ampliar engine Python de governanca sem enchimento artificial",
        ),
    )


def build_round_execution_package(
    platform_folder: Path,
    portfolio: EcosystemGovernancePortfolio,
    round_dossier: ExecutionRoundDossier | None = None,
    total_code_lines_analyzed: int = 0,
    code_lines_available: int = 0,
) -> RoundExecutionPackage:
    parsed_orders = discover_orders(platform_folder)
    decisions = tuple(decide_order(order, portfolio) for order in parsed_orders)
    candidates = portfolio.order_candidates
    minimums = build_round_minimums(decisions, candidates, total_code_lines_analyzed, code_lines_available)
    completed = sum(1 for decision in decisions if decision.final_status == OrderLifecycleStatus.COMPLETED)
    partial = sum(1 for decision in decisions if decision.final_status == OrderLifecycleStatus.PARTIAL)
    blocked = sum(1 for decision in decisions if decision.final_status == OrderLifecycleStatus.BLOCKED)
    active_after = tuple(candidate.candidate_id for candidate in candidates[:20])
    if round_dossier is not None:
        active_after = merge_unique(tuple(round_dossier.output_orders) + active_after)
    digest = stable_digest((platform_folder, [decision.compact_line for decision in decisions], active_after, utc_now()), length=10)
    return RoundExecutionPackage(
        round_id=f"mais-humana-lifecycle-{digest}",
        project_id="tudo-para-ia-mais-humana",
        parsed_orders=parsed_orders,
        decisions=decisions,
        minimums=minimums,
        output_candidates=candidates,
        active_after_round=active_after,
        completed_count=completed,
        partial_count=partial,
        blocked_count=blocked,
    )


def lifecycle_execution_markdown(package: RoundExecutionPackage) -> str:
    lines = [
        "# EXECUTADO - Fechamento de ordens ativas",
        "",
        f"- round_id: `{package.round_id}`",
        f"- project_id: `{package.project_id}`",
        f"- generated_at: `{package.generated_at}`",
        f"- status: `{package.success_label}`",
        f"- ordens lidas: `{len(package.parsed_orders)}`",
        f"- concluidas: `{package.completed_count}`",
        f"- parciais: `{package.partial_count}`",
        f"- bloqueadas: `{package.blocked_count}`",
        "",
        "## Minimos da rodada",
        "",
    ]
    for minimum in package.minimums:
        lines.append(
            f"- `{minimum.minimum_id}` {minimum.title}: `{minimum.actual_value}/{minimum.required_value}` "
            f"status `{minimum.status.value}` - {minimum.reason}"
        )
    lines.extend(["", "## Decisoes por ordem", ""])
    for decision in package.decisions:
        lines.append(f"### {decision.order.order_id}")
        lines.append("")
        lines.append(f"- arquivo: `{decision.order.path}`")
        lines.append(f"- tipo: `{decision.order.order_type.value}`")
        lines.append(f"- plataforma: `{decision.platform_id}`")
        lines.append(f"- status_final: `{decision.final_status.value}`")
        lines.append(f"- razao: {decision.reason}")
        if decision.resulting_candidates:
            lines.append("- candidatas vinculadas: " + ", ".join(f"`{item}`" for item in decision.resulting_candidates))
        if decision.pending_items:
            lines.append("- pendencias:")
            for pending in decision.pending_items:
                lines.append(f"  - {pending}")
        else:
            lines.append("- pendencias: nenhuma pendencia material associada")
        lines.append("")
    return "\n".join(lines).strip() + "\n"


def lifecycle_pending_markdown(package: RoundExecutionPackage) -> str:
    lines = ["# PENDENCIAS-CODEX - Fechamento de ordens ativas", ""]
    if package.pending_items:
        lines.extend(f"- {item}" for item in package.pending_items)
    else:
        lines.append("- Nenhuma pendencia material consolidada pelo lifecycle.")
    return "\n".join(lines).strip() + "\n"


def lifecycle_audit_markdown(package: RoundExecutionPackage) -> str:
    lines = [
        "# AUDITORIA-GPT - Fechamento de ordens ativas",
        "",
        "## Confirmado",
        "",
        f"- Ordens descobertas na pasta indicada: `{len(package.parsed_orders)}`",
        f"- Ordens concluidas: `{package.completed_count}`",
        f"- Ordens parciais: `{package.partial_count}`",
        f"- Ordens bloqueadas: `{package.blocked_count}`",
        f"- Ordens/candidatas ativas apos rodada: `{len(package.active_after_round)}`",
        "",
        "## Minimos",
        "",
    ]
    for minimum in package.minimums:
        lines.append(f"- `{minimum.minimum_id}`: `{minimum.status.value}` ({minimum.actual_value}/{minimum.required_value})")
    lines.extend(["", "## Evidencias por decisao", ""])
    for decision in package.decisions:
        lines.append(f"### {decision.order.order_id}")
        lines.append("")
        for evidence in decision.evidence[:8]:
            lines.append(f"- `{evidence.reference}` - {evidence.summary}")
        if not decision.evidence:
            lines.append("- nenhuma evidencia direta registrada")
        lines.append("")
    return "\n".join(lines).strip() + "\n"


def lifecycle_queue_markdown(package: RoundExecutionPackage) -> str:
    lines = ["# Fila ativa apos rodada", ""]
    if not package.active_after_round:
        lines.append("- Nenhuma ordem ativa de saida foi registrada.")
    for item in package.active_after_round:
        lines.append(f"- `{item}`")
    return "\n".join(lines).strip() + "\n"


def lifecycle_jsonable(package: RoundExecutionPackage) -> dict[str, object]:
    return package.to_dict()


def write_lifecycle_artifacts(platform_folder: Path, package: RoundExecutionPackage) -> tuple[Path, ...]:
    reports = platform_folder / "reports"
    audit = platform_folder / "audit"
    current = platform_folder / "current"
    indexes = platform_folder / "indexes"
    for folder in (reports, audit, current, indexes):
        folder.mkdir(parents=True, exist_ok=True)
    paths = {
        reports / "EXECUTADO__fechamento-ordens-ativas.md": lifecycle_execution_markdown(package),
        reports / "PENDENCIAS-CODEX__fechamento-ordens-ativas.md": lifecycle_pending_markdown(package),
        audit / "AUDITORIA-GPT__fechamento-ordens-ativas.md": lifecycle_audit_markdown(package),
        current / "active-order-queue.md": lifecycle_queue_markdown(package),
        indexes / "orders-lifecycle-index.md": lifecycle_execution_markdown(package),
    }
    written: list[Path] = []
    for path, content in paths.items():
        path.write_text(content, encoding="utf-8")
        written.append(path)
    return tuple(written)
