"""Recipe lifecycle: deploy / conflict / update (ADR-0019 V2).

Preferred authoring path for versioned recipes:

1. ``project.deploy_recipe(name, spec, cases=…)`` — create, no-op, or revise
2. ``get_or_create_recipe`` raises ``RecipeSpecConflictError`` on mismatch
3. Typed rebind with ``recipe.update(cases=…)`` when you hold a handle
4. Stale handle after concurrent revise → ``RecipeConflictError``

Default ``recipe.run()`` / ``run_recipe(recipe)`` uses ``inputs="pinned"``. Automatic
``inputs="latest"`` (re-admit against collection head) is not wired yet —
after new membership, re-admit via ``plan(..., case_grouping=…)``, then
``recipe.update(cases=upload.cases)`` or ``deploy_recipe(..., cases=…)``, then run.
"""

from __future__ import annotations

from datetime import datetime, timezone

from miura import Nexus
from miura.display import print_json
from miura.nexus.case_grouping.admission import CaseGroupingResult, MembershipCommitment
from miura.nexus.recipe.sigma_converter import RemoteSigmaConverter, SigmaClassRef
from miura.sdk import Schema
from miura.sdk.exceptions import RecipeConflictError, RecipeSpecConflictError
from miura.sdk.recipe import RecipeSpec


def _vtk_converter() -> RemoteSigmaConverter:
    return RemoteSigmaConverter(
        required_extensions=[".vtk"],
        reader=SigmaClassRef(class_name="vtkDataSetReader"),
        writer=SigmaClassRef(class_name="vtkXdmfWriter"),
    )


def _demo_cgr(result_id: str) -> CaseGroupingResult:
    """Construct an admitted-shaped pin for lifecycle demos (not a live CS admit)."""
    digest = "sha256:" + ("a" * 64)
    return CaseGroupingResult(
        result_id=result_id,
        membership_commitment=MembershipCommitment(
            canonical_membership_schema_version="1",
            case_group_stream_sha256=digest,
            member_edge_stream_sha256=digest,
            case_group_id_set_sha256=digest,
            membership_merkle_root=digest,
        ),
    )


def main() -> None:
    run_id = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S-%f")
    name = f"lifecycle-demo-{run_id}"

    with Nexus() as nexus:
        project = nexus.create_project(f"epico-recipe-lifecycle-{run_id}")
        schema = Schema.permissive()
        source = project.create_collection("source-vtk", schema=schema)
        target = project.create_collection("target-xdmf", schema=schema)

        spec_a = RecipeSpec.from_sigma_converter(
            source=source.name,
            target=target.name,
            converter=_vtk_converter(),
        )
        # Production code gets this handle from upload.cases, plan.cases, or
        # collection.case_grouping.admit(...).
        initial_cases = source.admitted_cases(_demo_cgr(f"cgr_initial_{run_id}"))
        print("Step 1: deploy_recipe creates the first revision")
        recipe = project.deploy_recipe(name, spec_a, cases=initial_cases)
        print(f"  recipe={recipe.name} revision={recipe.current_revision_id}")

        print("\nStep 2: deploy_recipe is a no-op when the effective spec matches")
        again = project.deploy_recipe(name, spec_a, cases=initial_cases)
        print(f"  same revision={again.current_revision_id}")

        print("\nStep 3: get_or_create_recipe conflicts on a different executable spec")
        # Source/target names are not part of the Converter execution payload —
        # change the reader so the effective executable spec actually differs.
        spec_b = RecipeSpec.from_sigma_converter(
            source=source.name,
            target=target.name,
            converter=RemoteSigmaConverter(
                required_extensions=[".vtk"],
                reader=SigmaClassRef(class_name="vtkXMLUnstructuredGridReader"),
                writer=SigmaClassRef(class_name="vtkXdmfWriter"),
            ),
        )
        try:
            project.get_or_create_recipe(name, spec_b)
            raise SystemExit("FAIL: expected RecipeSpecConflictError")
        except RecipeSpecConflictError as exc:
            print(f"  caught {type(exc).__name__}: {exc}")

        print("\nStep 4: deploy_recipe revises intentionally")
        revised = project.deploy_recipe(name, spec_b, cases=initial_cases)
        print(f"  new revision={revised.current_revision_id}")

        print("\nStep 5: recipe.update(cases=…) typed rebind")
        # In production this is upload.cases / plan.cases straight from admission.
        cgr = _demo_cgr(f"cgr_demo_{run_id}")
        cases = source.admitted_cases(cgr)
        rebound = revised.update(cases=cases)
        print(f"  rebound revision={rebound.current_revision_id} cases={cases.result_id}")

        print("\nStep 6: stale handle after revise raises RecipeConflictError")
        try:
            revised.update(cases=source.admitted_cases(_demo_cgr(f"cgr_stale_{run_id}")))
            raise SystemExit("FAIL: expected RecipeConflictError")
        except RecipeConflictError as exc:
            print(f"  caught {type(exc).__name__}: {exc}")

        print_json(
            {
                "name": rebound.name,
                "current_revision_id": rebound.current_revision_id,
                "effective_spec_commitment": rebound.effective_spec_commitment,
                "case_grouping_result_id": cgr.result_id,
            },
            title="Current recipe:",
        )

        print("\nLifecycle OK (deploy → conflict → deploy revise → update → stale CAS).")


if __name__ == "__main__":
    main()
