"""Launch/control service fixture builders for Wave 2 pre-extraction tests.

Spec refs:
- docs/proposals/execution-architecture-refactor.md#tests-required/consumer-conformance-tests
- docs/proposals/execution-architecture-refactor.md#tests-required/session-runtime-tests
- docs/proposals/execution-architecture-refactor.md#execution-contract-specific-design-rules/persona-provenance-and-subagent-inheritance
- docs/proposals/execution-architecture-refactor.md#refactor-program/wave-2-launch-and-control-unification
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Literal

from anima.app.control_service import (
    ApprovalRequest,
    ExistingReusablePersonaForInheritance,
    ExplicitChildPersonaOverride,
    InteractRequest,
    KillRequest,
    MaterializedReusablePersonaForInheritance,
    ResumeRequest,
    SpawnSubagentAccepted,
    SpawnSubagentOutcome,
    SpawnSubagentRejected,
    SpawnSubagentRequest,
    StatusRequest,
)
from anima.app.launch_service import (
    LaunchRequest,
    LaunchResult,
    LaunchSubagentRequest,
)
from anima.core.contracts import SpawnabilityReason
from anima.core.execution import ExecutionIntent
from anima.core.schemas import (
    AgentState,
    AgentStateData,
    ContextMode,
    JobSpec,
    LifecycleType,
)
from anima.core.schemas.models import PersonaSpec
from tests.fixtures.execution_contract_resolution import (
    make_consumer_conformance_fixture,
)


@dataclass(frozen=True)
class LaunchRequestFixtureSet:
    """Canonical launch requests for run/spawn/chat entrypoints."""

    run: LaunchRequest
    spawn: LaunchRequest
    chat: LaunchRequest


@dataclass(frozen=True)
class ControlRequestFixtureSet:
    """Canonical control requests for status/interact/approval/lifecycle actions."""

    status: StatusRequest
    interact: InteractRequest
    approve: ApprovalRequest
    reject: ApprovalRequest
    resume: ResumeRequest
    kill: KillRequest


@dataclass(frozen=True)
class SpawnSubagentFixtureCase:
    """Spawn-subagent fixture case with expected app-layer business outcome."""

    case_id: str
    provenance_source: Literal["file", "inline", "synthesized", "snapshot"]
    materialization_policy: Literal[
        "inherit_reusable_persona_ref",
        "inherit_materialized_snapshot",
        "reject_child_inheritance",
    ]
    request: SpawnSubagentRequest
    outcome: SpawnSubagentOutcome


@dataclass(frozen=True)
class ResolutionRegressionFixture:
    """Observed duplicate-resolution regression expectations per adapter path."""

    bug_path: Literal["run_spawn", "mcp_control"]
    expected_resolution_calls: int
    expected_runtime_resolution_calls: int


def make_launch_request_fixture_set(
    instance_id: str = "inst_launch_fixture",
) -> LaunchRequestFixtureSet:
    """Build run/spawn/chat launch requests that share one execution intent."""
    persona = PersonaSpec(
        id="fixture-persona",
        description="Fixture persona",
        prompt="You are fixture persona",
        model="fixture-model",
    )
    intent = ExecutionIntent(
        persona_spec=persona,
        job_spec=JobSpec(persona_ref="personas/fixture.json", instance_id=instance_id),
    )
    return LaunchRequestFixtureSet(
        run=LaunchRequest(
            origin="run",
            task="run fixture task",
            execution_intent=intent,
            instance_id=f"{instance_id}_run",
            trace_id="tr_launch_fixture",
        ),
        spawn=LaunchRequest(
            origin="spawn",
            task="spawn fixture task",
            execution_intent=intent,
            instance_id=f"{instance_id}_spawn",
            trace_id="tr_launch_fixture",
        ),
        chat=LaunchRequest(
            origin="chat",
            task="chat fixture task",
            execution_intent=intent,
            instance_id=f"{instance_id}_chat",
            trace_id="tr_launch_fixture",
        ),
    )


def make_control_request_fixture_set(
    instance_id: str = "inst_control_fixture",
) -> ControlRequestFixtureSet:
    """Build status/interact/approve/reject/resume/kill control request fixtures."""
    return ControlRequestFixtureSet(
        status=StatusRequest(instance_id=instance_id),
        interact=InteractRequest(instance_id=instance_id, prompt="fixture prompt"),
        approve=ApprovalRequest(
            instance_id=instance_id,
            approved=True,
            note="Approved by fixture",
        ),
        reject=ApprovalRequest(
            instance_id=instance_id,
            approved=False,
            note="Rejected by fixture",
        ),
        resume=ResumeRequest(instance_id=instance_id),
        kill=KillRequest(instance_id=instance_id, reason="fixture kill"),
    )


def _make_launch_result(instance_id: str) -> LaunchResult:
    case = make_consumer_conformance_fixture(instance_id=instance_id)
    return LaunchResult(
        instance_id=instance_id,
        trace_id="tr_spawn_fixture",
        resolved_contract=case.contract,
        initial_state=AgentStateData(
            instance_id=instance_id,
            trace_id="tr_spawn_fixture",
            persona_name="fixture-child",
            persona_ref="personas/fixture-child.json",
            state=AgentState.pending,
            lifecycle=LifecycleType.ephemeral,
        ),
    )


def make_ctl_spawn_subagent_fixture_cases(
    parent_instance_id: str = "inst_parent_fixture",
) -> tuple[SpawnSubagentFixtureCase, ...]:
    """Build spawn fixtures for file/inline/synthesized/materialized provenance paths."""
    accepted_launch = _make_launch_result("inst_child_fixture")
    launch_request = LaunchSubagentRequest(
        task="child fixture task",
        instance_id="inst_child_fixture",
        trace_id="tr_spawn_fixture",
        parent_instance_id=parent_instance_id,
        child_persona_ref="personas/child.json",
        child_persona=PersonaSpec(
            id="child",
            description="Fixture child persona",
            prompt="You are a fixture child persona",
            model="fixture-model",
        ),
        child_job=JobSpec(
            persona_ref="personas/child.json",
            instance_id="inst_child_fixture",
            context_mode=ContextMode.isolated,
        ),
        resolved_contract=accepted_launch.resolved_contract,
    )

    file_backed_case = SpawnSubagentFixtureCase(
        case_id="file_backed_inheritance",
        provenance_source="file",
        materialization_policy="inherit_reusable_persona_ref",
        request=SpawnSubagentRequest(
            parent_instance_id=parent_instance_id,
            task="spawn from file-backed parent",
        ),
        outcome=SpawnSubagentAccepted(
            kind="spawned",
            launch_request=launch_request,
            launch_result=accepted_launch,
            persona_resolution=ExistingReusablePersonaForInheritance(
                reusable_persona_ref="personas/parent.json",
                source="file",
            ),
        ),
    )

    inline_materialized_case = SpawnSubagentFixtureCase(
        case_id="inline_materialized_inheritance",
        provenance_source="inline",
        materialization_policy="inherit_materialized_snapshot",
        request=SpawnSubagentRequest(
            parent_instance_id=parent_instance_id,
            task="spawn from inline parent via materialization",
        ),
        outcome=SpawnSubagentAccepted(
            kind="spawned",
            launch_request=launch_request,
            launch_result=accepted_launch,
            persona_resolution=MaterializedReusablePersonaForInheritance(
                reusable_persona_ref="persona://materialized/inline-parent",
                source="inline",
            ),
        ),
    )

    synthesized_materialized_case = SpawnSubagentFixtureCase(
        case_id="synthesized_materialized_inheritance",
        provenance_source="synthesized",
        materialization_policy="inherit_materialized_snapshot",
        request=SpawnSubagentRequest(
            parent_instance_id=parent_instance_id,
            task="spawn from synthesized parent via materialization",
        ),
        outcome=SpawnSubagentAccepted(
            kind="spawned",
            launch_request=launch_request,
            launch_result=accepted_launch,
            persona_resolution=MaterializedReusablePersonaForInheritance(
                reusable_persona_ref="persona://materialized/synth-parent",
                source="synthesized",
            ),
        ),
    )

    materialized_snapshot_case = SpawnSubagentFixtureCase(
        case_id="snapshot_inheritance",
        provenance_source="snapshot",
        materialization_policy="inherit_materialized_snapshot",
        request=SpawnSubagentRequest(
            parent_instance_id=parent_instance_id,
            task="spawn from materialized snapshot parent",
        ),
        outcome=SpawnSubagentAccepted(
            kind="spawned",
            launch_request=launch_request,
            launch_result=accepted_launch,
            persona_resolution=ExistingReusablePersonaForInheritance(
                reusable_persona_ref="persona://snapshot/parent",
                source="snapshot",
            ),
        ),
    )

    inline_rejected_case = SpawnSubagentFixtureCase(
        case_id="inline_rejected_non_spawnable",
        provenance_source="inline",
        materialization_policy="reject_child_inheritance",
        request=SpawnSubagentRequest(
            parent_instance_id=parent_instance_id,
            task="spawn rejected for inline parent",
        ),
        outcome=SpawnSubagentRejected(
            kind="spawnability_error",
            reason=SpawnabilityReason(
                code="inline_persona_requires_materialization",
                message=(
                    "Inline persona is not spawnable until materialized as a reusable snapshot."
                ),
                user_action="Retry with explicit materialization policy or explicit child persona.",
                retryable=True,
                details={"source": "inline"},
            ),
        ),
    )

    synthesized_rejected_case = SpawnSubagentFixtureCase(
        case_id="synthesized_rejected_non_spawnable",
        provenance_source="synthesized",
        materialization_policy="reject_child_inheritance",
        request=SpawnSubagentRequest(
            parent_instance_id=parent_instance_id,
            task="spawn rejected for synthesized parent",
        ),
        outcome=SpawnSubagentRejected(
            kind="spawnability_error",
            reason=SpawnabilityReason(
                code="synthesized_persona_requires_materialization",
                message=(
                    "Synthesized persona is not spawnable until materialized as a reusable snapshot."
                ),
                user_action="Retry with explicit materialization policy or explicit child persona.",
                retryable=True,
                details={"source": "synthesized"},
            ),
        ),
    )

    return (
        file_backed_case,
        inline_materialized_case,
        synthesized_materialized_case,
        materialized_snapshot_case,
        inline_rejected_case,
        synthesized_rejected_case,
    )


def make_run_spawn_resolution_regression_fixture() -> ResolutionRegressionFixture:
    """Regression fixture for duplicated run/spawn contract-resolution behavior."""
    return ResolutionRegressionFixture(
        bug_path="run_spawn",
        expected_resolution_calls=1,
        expected_runtime_resolution_calls=1,
    )


def make_mcp_control_resolution_regression_fixture() -> ResolutionRegressionFixture:
    """Regression fixture for duplicated MCP control-path runtime resolution behavior."""
    return ResolutionRegressionFixture(
        bug_path="mcp_control",
        expected_resolution_calls=1,
        expected_runtime_resolution_calls=1,
    )


def cli_spawn_outcome_view(outcome: SpawnSubagentOutcome) -> dict[str, object]:
    """Map shared SpawnSubagentOutcome into a CLI-friendly payload fixture."""
    if isinstance(outcome, SpawnSubagentAccepted):
        return {
            "status": "spawned",
            "instance_id": outcome.launch_result.instance_id,
            "trace_id": outcome.launch_result.trace_id,
            "persona_resolution": type(outcome.persona_resolution).__name__,
        }
    return {
        "status": "spawnability_error",
        "reason": outcome.reason.code,
        "message": outcome.reason.message,
        "retryable": outcome.reason.retryable,
    }


def mcp_spawn_outcome_view(outcome: SpawnSubagentOutcome) -> dict[str, object]:
    """Map shared SpawnSubagentOutcome into an MCP-friendly payload fixture."""
    if isinstance(outcome, SpawnSubagentAccepted):
        return {
            "kind": "spawned",
            "instance_id": outcome.launch_result.instance_id,
            "trace_id": outcome.launch_result.trace_id,
            "resolution": type(outcome.persona_resolution).__name__,
        }
    return {
        "kind": "spawnability_error",
        "error": {
            "code": outcome.reason.code,
            "message": outcome.reason.message,
            "retryable": outcome.reason.retryable,
        },
    }


def control_request_name_for_cli(request: object) -> str:
    """Map control request object to expected CLI command label."""
    if isinstance(request, StatusRequest):
        return "status"
    if isinstance(request, InteractRequest):
        return "talk"
    if isinstance(request, ApprovalRequest):
        return "approve" if request.approved else "reject"
    if isinstance(request, ResumeRequest):
        return "resume"
    if isinstance(request, KillRequest):
        return "kill"
    raise TypeError(f"Unsupported request type: {type(request)!r}")


def control_request_name_for_mcp(request: object) -> str:
    """Map control request object to expected MCP tool label."""
    if isinstance(request, StatusRequest):
        return "anima.status"
    if isinstance(request, InteractRequest):
        return "anima.interact"
    if isinstance(request, ApprovalRequest):
        return "anima.approve" if request.approved else "anima.reject"
    if isinstance(request, ResumeRequest):
        return "anima.resume"
    if isinstance(request, KillRequest):
        return "anima.kill"
    raise TypeError(f"Unsupported request type: {type(request)!r}")


def launch_result_summary(result: LaunchResult) -> dict[str, str]:
    """Build a compact launch-result summary shared by CLI and MCP tests."""
    return {
        "instance_id": result.instance_id,
        "trace_id": result.trace_id,
        "effective_model": result.resolved_contract.runtime.effective_model,
        "state": result.initial_state.state.value,
    }


def override_persona_fixture_for_spawn() -> ExplicitChildPersonaOverride:
    """Dedicated fixture for explicit child-persona override branch."""
    return ExplicitChildPersonaOverride(persona_ref="personas/child-override.json")


__all__ = [
    "ControlRequestFixtureSet",
    "LaunchRequestFixtureSet",
    "ResolutionRegressionFixture",
    "SpawnSubagentFixtureCase",
    "cli_spawn_outcome_view",
    "control_request_name_for_cli",
    "control_request_name_for_mcp",
    "launch_result_summary",
    "make_control_request_fixture_set",
    "make_ctl_spawn_subagent_fixture_cases",
    "make_launch_request_fixture_set",
    "make_mcp_control_resolution_regression_fixture",
    "make_run_spawn_resolution_regression_fixture",
    "mcp_spawn_outcome_view",
    "override_persona_fixture_for_spawn",
]
