"""Shadow replay and semantic-equivalence reporting for GenerationPlan.v1."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Literal

from pydantic import BaseModel, ConfigDict, Field

from src.domain.app_blocks.registry import load_default_registry
from src.domain.app_blocks.schemas import AppBlockPlan
from src.domain.codegen.generation_plan import (
    GenerationPlanV1,
    compile_generation_plan,
)
from src.domain.projects.discovery_schemas import DiscoveryResult

Equivalence = Literal[
    "equivalent",
    "new_plan_more_specific",
    "legacy_plan_more_specific",
    "semantic_mismatch",
]


@dataclass(frozen=True)
class ShadowReplayCase:
    application_id: str
    discovery: DiscoveryResult
    app_block_plan: AppBlockPlan
    expected_blocks: frozenset[str] = frozenset()
    expected_feature_packs: frozenset[str] = frozenset()


class ShadowReplayRow(BaseModel):
    model_config = ConfigDict(extra="forbid")

    application_id: str
    equivalence: Equivalence
    catalog_valid: bool = True
    generation_plan_hash: str | None = None
    missing_blocks: list[str] = Field(default_factory=list)
    missing_feature_packs: list[str] = Field(default_factory=list)
    missing_cloud_capabilities: list[str] = Field(default_factory=list)
    details: list[str] = Field(default_factory=list)


class ShadowReplayReport(BaseModel):
    model_config = ConfigDict(extra="forbid")

    schema_version: str = "generation_plan_shadow_replay.v1"
    applications: int
    compiled: int
    catalog_valid: int
    equivalent: int
    new_plan_more_specific: int
    legacy_plan_more_specific: int
    semantic_mismatches: int
    rows: list[ShadowReplayRow] = Field(default_factory=list)

    @property
    def passed(self) -> bool:
        return (
            self.applications == self.compiled == self.catalog_valid
            and self.semantic_mismatches == 0
            and self.legacy_plan_more_specific == 0
        )


def replay_generation_plans(cases: list[ShadowReplayCase]) -> ShadowReplayReport:
    rows: list[ShadowReplayRow] = []
    for case in cases:
        try:
            plan = compile_generation_plan(case.discovery, case.app_block_plan)
            row = _compare(case, plan)
        except Exception as exc:  # noqa: BLE001 - report one failed case, continue corpus
            row = ShadowReplayRow(
                application_id=case.application_id,
                equivalence="semantic_mismatch",
                catalog_valid=False,
                details=[f"replay_failed:{type(exc).__name__}:{exc}"],
            )
        rows.append(row)

    return ShadowReplayReport(
        applications=len(rows),
        compiled=sum(bool(row.generation_plan_hash) for row in rows),
        catalog_valid=sum(row.catalog_valid for row in rows),
        equivalent=sum(row.equivalence == "equivalent" for row in rows),
        new_plan_more_specific=sum(
            row.equivalence == "new_plan_more_specific" for row in rows
        ),
        legacy_plan_more_specific=sum(
            row.equivalence == "legacy_plan_more_specific" for row in rows
        ),
        semantic_mismatches=sum(
            row.equivalence == "semantic_mismatch" for row in rows
        ),
        rows=rows,
    )


def _compare(case: ShadowReplayCase, plan: GenerationPlanV1) -> ShadowReplayRow:
    old_blocks = set(case.expected_blocks) or {
        block.id for block in case.app_block_plan.blocks
    }
    old_packs = set(case.expected_feature_packs) or set(
        case.app_block_plan.feature_pack_ids
        or case.app_block_plan.required_feature_packs
    )
    new_blocks = set(plan.composition.app_block_ids)
    new_packs = set(plan.composition.feature_pack_ids)
    known_block_ids = {block.id for block in load_default_registry().all()}
    old_cloud = set(case.app_block_plan.requested_capabilities) - known_block_ids
    new_cloud = set(plan.composition.cloud_capability_ids)
    missing_blocks = sorted(old_blocks - new_blocks)
    missing_packs = sorted(old_packs - new_packs)
    missing_cloud = sorted(old_cloud - new_cloud)
    missing = missing_blocks or missing_packs or missing_cloud
    added = (
        (new_blocks - old_blocks)
        or (new_packs - old_packs)
        or (new_cloud - old_cloud)
    )
    equivalence: Equivalence
    if missing:
        equivalence = "legacy_plan_more_specific"
    elif added:
        equivalence = "new_plan_more_specific"
    else:
        equivalence = "equivalent"
    return ShadowReplayRow(
        application_id=case.application_id,
        equivalence=equivalence,
        generation_plan_hash=plan.provenance.plan_hash,
        missing_blocks=missing_blocks,
        missing_feature_packs=missing_packs,
        missing_cloud_capabilities=missing_cloud,
        details=["catalog_referential_integrity=pass"],
    )


def render_shadow_replay_markdown(report: ShadowReplayReport) -> str:
    status = "PASS" if report.passed else "FAIL"
    lines = [
        "# GenerationPlan.v1 Shadow Replay",
        "",
        f"Status: **{status}**",
        "",
        f"- Applications: {report.applications}",
        f"- Compiled: {report.compiled}",
        f"- Catalog valid: {report.catalog_valid}",
        f"- Equivalent: {report.equivalent}",
        f"- New plan more specific: {report.new_plan_more_specific}",
        f"- Legacy plan more specific: {report.legacy_plan_more_specific}",
        f"- Semantic mismatches: {report.semantic_mismatches}",
        "",
        "| Application | Result | Plan hash | Details |",
        "| --- | --- | --- | --- |",
    ]
    for row in report.rows:
        details = "; ".join(row.details)
        if row.missing_blocks:
            details += f"; missing_blocks={','.join(row.missing_blocks)}"
        if row.missing_feature_packs:
            details += f"; missing_feature_packs={','.join(row.missing_feature_packs)}"
        lines.append(
            f"| `{row.application_id}` | `{row.equivalence}` | "
            f"`{row.generation_plan_hash or '-'}` | {details or '-'} |"
        )
    return "\n".join(lines) + "\n"


__all__ = [
    "ShadowReplayCase",
    "ShadowReplayReport",
    "replay_generation_plans",
    "render_shadow_replay_markdown",
]
