"""Read and write installed composition snapshots for later codegen turns."""

from __future__ import annotations

from uuid import UUID

from sqlalchemy import select

from src.domain.codegen.generation_plan import GenerationPlanV1
from src.domain.composition_recipes.installed_models import InstalledCompositionSnapshotRecord
from src.domain.composition_recipes.snapshots import (
    InstalledCompositionSnapshot,
    InstalledFeaturePack,
    snapshot_from_generation_plan,
)


class InstalledCompositionRepository:
    def __init__(self, db):
        self.db = db

    async def latest(self, project_id: UUID) -> InstalledCompositionSnapshotRecord | None:
        result = await self.db.execute(
            select(InstalledCompositionSnapshotRecord)
            .where(InstalledCompositionSnapshotRecord.project_id == project_id)
            .order_by(InstalledCompositionSnapshotRecord.created_at.desc())
            .limit(1)
        )
        return result.scalar_one_or_none()

    async def add(self, snapshot: InstalledCompositionSnapshot) -> None:
        self.db.add(
            InstalledCompositionSnapshotRecord(
                project_id=snapshot.project_id,
                recipe_id=snapshot.recipe_id,
                recipe_version=snapshot.recipe_version,
                starter_id=snapshot.starter_id,
                starter_version=snapshot.starter_version,
                feature_packs=[item.model_dump(mode="json") for item in snapshot.feature_packs],
                app_blocks=list(snapshot.app_blocks),
                capability_contract_versions=dict(snapshot.capability_contract_versions),
                auth_contract=snapshot.auth_contract,
                auth_contract_version=snapshot.auth_contract_version,
                runtime_contract_version=snapshot.runtime_contract_version,
                reaction_contract_version=snapshot.reaction_contract_version,
                generation_plan_hash=snapshot.generation_plan_hash,
                generated_artifact_version=snapshot.generated_artifact_version,
            )
        )
        await self.db.flush()


def snapshot_model_from_record(
    row: InstalledCompositionSnapshotRecord,
) -> InstalledCompositionSnapshot:
    packs = [
        InstalledFeaturePack.model_validate(item)
        for item in (row.feature_packs or [])
        if isinstance(item, dict)
    ]
    return InstalledCompositionSnapshot(
        project_id=row.project_id,
        recipe_id=row.recipe_id,
        recipe_version=row.recipe_version,
        starter_id=row.starter_id,
        starter_version=row.starter_version,
        feature_packs=tuple(packs),
        app_blocks=tuple(str(item) for item in (row.app_blocks or [])),
        capability_contract_versions=dict(row.capability_contract_versions or {}),
        auth_contract=row.auth_contract,
        auth_contract_version=row.auth_contract_version,
        runtime_contract_version=row.runtime_contract_version,
        reaction_contract_version=row.reaction_contract_version,
        generation_plan_hash=row.generation_plan_hash,
        generated_artifact_version=row.generated_artifact_version,
        created_at=row.created_at,
    )


def baseline_from_snapshot(snapshot: InstalledCompositionSnapshot) -> dict:
    return {
        "composition": {
            "app_block_ids": list(snapshot.app_blocks),
            "feature_pack_ids": [item.canonical_id for item in snapshot.feature_packs],
            "primitive_ids": [],
            "cloud_capability_ids": sorted(snapshot.capability_contract_versions),
            "recipe_ids": [snapshot.recipe_id] if snapshot.recipe_id else [],
            "recipe_versions": (
                {snapshot.recipe_id: snapshot.recipe_version}
                if snapshot.recipe_id and snapshot.recipe_version
                else {}
            ),
            "form_definitions": {},
        },
        "runtime": {"starter_contract_ids": []},
    }


def overlay_snapshot_on_plan(plan: dict | None, snapshot: InstalledCompositionSnapshot) -> dict:
    """Installed blocks and packs win over a freshly restamped catalog plan."""
    base = baseline_from_snapshot(snapshot)
    if not isinstance(plan, dict):
        return base
    composition = dict(plan.get("composition") or {})
    snap = base["composition"]
    composition["app_block_ids"] = list(snap["app_block_ids"])
    composition["feature_pack_ids"] = list(snap["feature_pack_ids"])
    if snap["recipe_ids"]:
        composition["recipe_ids"] = list(snap["recipe_ids"])
        composition["recipe_versions"] = dict(snap["recipe_versions"])
    merged = dict(plan)
    merged["composition"] = composition
    return merged


def snapshot_write_decision(
    *,
    has_existing: bool,
    explicit_block_ids: list[str],
    blocks_changed: bool,
    initial: bool,
) -> bool:
    """First build writes once. Later turns write only for an admitted block change."""
    if initial:
        return not has_existing
    if not explicit_block_ids:
        return False
    if not has_existing:
        return True
    return blocks_changed


async def overlay_installed_baseline(db, project_id: UUID, plan: dict | None) -> dict | None:
    row = await InstalledCompositionRepository(db).latest(project_id)
    if row is None:
        return plan
    return overlay_snapshot_on_plan(plan, snapshot_model_from_record(row))


async def write_initial_snapshot(db, project_id: UUID, plan: GenerationPlanV1) -> bool:
    repo = InstalledCompositionRepository(db)
    existing = await repo.latest(project_id)
    if not snapshot_write_decision(
        has_existing=existing is not None,
        explicit_block_ids=[],
        blocks_changed=False,
        initial=True,
    ):
        return False
    await repo.add(snapshot_from_generation_plan(project_id, plan))
    return True


async def refresh_snapshot_for_explicit_change(
    db,
    project_id: UUID,
    plan_dict: dict,
    *,
    explicit_block_ids: list[str],
) -> bool:
    repo = InstalledCompositionRepository(db)
    existing = await repo.latest(project_id)
    built = None
    blocks_changed = True
    if existing is not None and explicit_block_ids:
        try:
            plan = GenerationPlanV1.model_validate(plan_dict)
        except Exception:
            return False
        built = snapshot_from_generation_plan(project_id, plan)
        current = snapshot_model_from_record(existing)
        blocks_changed = list(current.app_blocks) != list(built.app_blocks) or [
            item.canonical_id for item in current.feature_packs
        ] != [item.canonical_id for item in built.feature_packs]
    if not snapshot_write_decision(
        has_existing=existing is not None,
        explicit_block_ids=list(explicit_block_ids),
        blocks_changed=blocks_changed,
        initial=False,
    ):
        return False
    if built is None:
        try:
            plan = GenerationPlanV1.model_validate(plan_dict)
        except Exception:
            return False
        built = snapshot_from_generation_plan(project_id, plan)
    await repo.add(built)
    return True
