"""Runtime proof for each declared Fast Lane application.

FL-D4 proves composition.  This module closes the missing evidence layer by
materializing every declared application from its declared Job Feature Packs
and exercising each reachable record surface through the same generated-app
kernel used by the 12H certification harness.
"""

from __future__ import annotations

import json
import shutil
from dataclasses import dataclass, field
from pathlib import Path

from src.domain.app_blocks.catalogs import default_known_packs, default_primitives
from src.domain.app_blocks.execution import assert_install_plan_installable
from src.domain.app_blocks.generated_product_conformance_12h import (
    ENTITY_BY_BLOCK,
    PlatformKernel,
    _authority_scan,
    _seed_defaults,
    materialize_generated_product,
    plan_digest,
    scan_forbidden,
)
from src.domain.app_blocks.job_packs import JOB_FEATURE_PACKS
from src.domain.app_blocks.planner import ProductIntent, default_slots
from src.domain.app_blocks.presentation_coverage import DeclaredApplication, load_presentation_coverage
from src.domain.app_blocks.registry import load_default_registry
from src.domain.app_blocks.resolver import planned_from_manifest, resolve_block
from src.domain.app_blocks.schemas import AppBlockPlan

ROOT = Path(__file__).resolve().parents[3]
DEFAULT_ARTIFACT = ROOT / "docs/audits/fixtures/fast_lane_runtime_proof.json"


@dataclass(frozen=True)
class DeclaredRuntimeProof:
    application_id: str
    starter: str
    required_blocks: tuple[str, ...]
    generated_blocks: tuple[str, ...]
    runtime_result: str
    persisted_records: int
    read_back_records: int
    findings: tuple[str, ...] = ()
    plan_digest: str = ""
    generated_project: str = ""

    @property
    def runtime_proven(self) -> bool:
        return self.runtime_result == "pass" and not self.findings


def _intent(app: DeclaredApplication, blocks: list[str]) -> ProductIntent:
    purpose = f"Declared Fast Lane application {app.id}: " + ", ".join(blocks)
    return ProductIntent(
        purpose=purpose,
        first_build_goal=purpose,
        actor_names=["Staff admin"],
        workflow_names=["Create, edit, list, and read back application records"],
        entity_names=[ENTITY_BY_BLOCK[item] for item in blocks if item in ENTITY_BY_BLOCK],
        authentication_required=True,
        staff_review=True,
    )


def _declared_plan(app: DeclaredApplication) -> AppBlockPlan:
    registry = load_default_registry()
    roots: list[str] = []
    for pack_id in app.packs:
        pack = JOB_FEATURE_PACKS[pack_id]
        roots.extend(pack.required_app_blocks)
    queue = list(dict.fromkeys(roots))
    intent = _intent(app, queue)
    blocks = []
    packs: list[str] = []
    primitives: list[str] = []
    gaps = []
    configs = []
    starter_contracts: list[str] = []
    seen: set[str] = set()
    while queue:
        block_id = queue.pop(0)
        if block_id in seen:
            continue
        seen.add(block_id)
        manifest = registry.require(block_id)
        found_packs, found_primitives, nested, found_gaps, found_configs, optionals = resolve_block(
            manifest,
            registry,
            primitives=default_primitives(),
            known_packs=default_known_packs(),
        )
        packs.extend(found_packs)
        primitives.extend(found_primitives)
        gaps.extend(found_gaps)
        configs.extend(found_configs)
        starter_contracts.extend(slot.component_contract for slot in manifest.slots if slot.component_contract)
        blocks.append(planned_from_manifest(manifest, f"Declared application {app.id}.", default_slots(manifest, intent), optionals))
        queue.extend(item for item in nested if item not in seen)
    return AppBlockPlan(
        requested_capabilities=roots,
        blocks=blocks,
        required_feature_packs=sorted(set(packs)),
        required_primitives=sorted(set(primitives)),
        configuration_requirements=configs,
        gaps=list({json.dumps(item.model_dump(), sort_keys=True): item for item in gaps}.values()),
        starter_contracts=sorted(set(starter_contracts)),
        starter_id=app.starter,
        feature_pack_ids=app.packs,
        forbidden_capabilities=app.forbidden_implicit,
    )


def _exercise_records(app: DeclaredApplication, plan: AppBlockPlan) -> tuple[int, int, list[str]]:
    kernel = PlatformKernel(namespace=f"fast-lane-{app.id}")
    findings: list[str] = []
    persisted = 0
    read_back = 0
    kernel.as_staff()
    for block in plan.blocks:
        entity_key = ENTITY_BY_BLOCK.get(block.id)
        if not entity_key or entity_key in {"order", "invoice_record"}:
            continue
        try:
            data = {"name": f"{app.id} proof record"}
            if entity_key == "file_attachment":
                data.update(
                    {
                        "file_id": f"file_fast-lane-{app.id}",
                        "subject_entity_key": "proof",
                        "subject_record_id": app.id,
                    }
                )
            created = _seed_defaults(kernel, entity_key, **data)
            persisted += 1
            loaded = kernel.get_record(entity_key, created["id"])
            if loaded["id"] == created["id"]:
                read_back += 1
            else:
                findings.append(f"readback_mismatch:{block.id}")
        except Exception as exc:  # noqa: BLE001 - proof records the failed surface
            findings.append(f"record_failure:{block.id}:{exc}")
    return persisted, read_back, findings


def prove_declared_runtime(base: str | Path, *, starter: str = "nuxt_app") -> list[DeclaredRuntimeProof]:
    base = Path(base)
    base.mkdir(parents=True, exist_ok=True)
    report = load_presentation_coverage()
    proofs: list[DeclaredRuntimeProof] = []
    for app in report.declared_applications:
        dest = base / app.id
        if dest.exists():
            shutil.rmtree(dest)
        findings: list[str] = []
        plan = _declared_plan(app)
        required = {block for pack_id in app.packs for block in JOB_FEATURE_PACKS[pack_id].required_app_blocks}
        generated = {block.id for block in plan.blocks}
        missing = sorted(required - generated)
        if missing:
            findings.append("missing_declared_blocks:" + ",".join(missing))
        try:
            assert_install_plan_installable(plan)
            surfaces = materialize_generated_product(dest, plan=plan, starter=starter)
            private_count, extras = scan_forbidden(dest, plan)
            findings.extend(_authority_scan(dest, plan, surfaces))
            if private_count:
                findings.append(f"private_substitute_markers:{private_count}")
            if extras:
                findings.append("unexpected:" + ",".join(extras))
            persisted, read_back, record_findings = _exercise_records(app, plan)
            findings.extend(record_findings)
            if persisted != read_back:
                findings.append("persist_readback_count_mismatch")
            result = "pass" if not findings else "fail"
        except Exception as exc:  # noqa: BLE001 - proof records the failed app
            persisted, read_back = 0, 0
            findings.append(f"generation_failure:{exc}")
            result = "fail"
        proofs.append(
            DeclaredRuntimeProof(
                application_id=app.id,
                starter=starter,
                required_blocks=tuple(sorted(required)),
                generated_blocks=tuple(sorted(generated)),
                runtime_result=result,
                persisted_records=persisted,
                read_back_records=read_back,
                findings=tuple(findings),
                plan_digest=plan_digest(plan),
                generated_project=str(dest),
            )
        )
    return proofs


def write_artifact(proofs: list[DeclaredRuntimeProof], path: Path = DEFAULT_ARTIFACT) -> Path:
    path.parent.mkdir(parents=True, exist_ok=True)
    payload = {
        "phase": "FL-D5",
        "status": "runtime_proven" if all(item.runtime_proven for item in proofs) else "runtime_proof_incomplete",
        "declared_applications": len(proofs),
        "runtime_proven": sum(item.runtime_proven for item in proofs),
        "applications": [item.__dict__ for item in proofs],
    }
    path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8", newline="\n")
    return path
