"""Launch/control service fixture conformance tests before service extraction.

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 returns.result import Success

from anima.app.control_service import (
    ApprovalRequest,
    DefaultControlService,
    KillRequest,
    ResumeRequest,
    SpawnPersonaPort,
    SpawnSubagentRequest,
    SpawnSubagentAccepted,
    SpawnSubagentRejected,
)
from anima.core.contracts import PersonaSpawnability, SpawnabilityReason
from tests.fixtures.launch_control_services import (
    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,
)


def test_launch_request_fixture_set_covers_run_spawn_chat() -> None:
    fixtures = make_launch_request_fixture_set("inst_launch_matrix")

    assert fixtures.run.origin == "run"
    assert fixtures.spawn.origin == "spawn"
    assert fixtures.chat.origin == "chat"

    assert fixtures.run.trace_id == fixtures.spawn.trace_id
    assert fixtures.spawn.trace_id == fixtures.chat.trace_id
    assert fixtures.run.execution_intent == fixtures.spawn.execution_intent
    assert fixtures.spawn.execution_intent == fixtures.chat.execution_intent


def test_control_request_fixture_set_covers_status_interact_approval_resume_kill() -> (
    None
):
    fixtures = make_control_request_fixture_set("inst_control_matrix")

    assert control_request_name_for_cli(fixtures.status) == "status"
    assert control_request_name_for_cli(fixtures.interact) == "talk"
    assert control_request_name_for_cli(fixtures.approve) == "approve"
    assert control_request_name_for_cli(fixtures.reject) == "reject"
    assert control_request_name_for_cli(fixtures.resume) == "resume"
    assert control_request_name_for_cli(fixtures.kill) == "kill"

    assert control_request_name_for_mcp(fixtures.status) == "anima.status"
    assert control_request_name_for_mcp(fixtures.interact) == "anima.interact"
    assert control_request_name_for_mcp(fixtures.approve) == "anima.approve"
    assert control_request_name_for_mcp(fixtures.reject) == "anima.reject"
    assert control_request_name_for_mcp(fixtures.resume) == "anima.resume"
    assert control_request_name_for_mcp(fixtures.kill) == "anima.kill"


def test_ctl_spawn_subagent_fixtures_cover_file_inline_synthesized_and_snapshot() -> (
    None
):
    cases = make_ctl_spawn_subagent_fixture_cases("inst_parent_matrix")
    sources = {case.provenance_source for case in cases}

    assert {"file", "inline", "synthesized", "snapshot"}.issubset(sources)


def test_spawnability_fixtures_prove_materialize_or_structured_rejection() -> None:
    cases = make_ctl_spawn_subagent_fixture_cases("inst_parent_spawnability")

    inline_cases = [case for case in cases if case.provenance_source == "inline"]
    synthesized_cases = [
        case for case in cases if case.provenance_source == "synthesized"
    ]

    assert any(isinstance(case.outcome, SpawnSubagentAccepted) for case in inline_cases)
    assert any(isinstance(case.outcome, SpawnSubagentRejected) for case in inline_cases)
    assert any(
        isinstance(case.outcome, SpawnSubagentAccepted) for case in synthesized_cases
    )
    assert any(
        isinstance(case.outcome, SpawnSubagentRejected) for case in synthesized_cases
    )

    rejected = [
        case for case in cases if isinstance(case.outcome, SpawnSubagentRejected)
    ]
    assert rejected
    for case in rejected:
        reason = case.outcome.reason
        assert reason.code in {
            "inline_persona_requires_materialization",
            "synthesized_persona_requires_materialization",
        }
        assert len(reason.message) > 0
        assert len(reason.user_action or "") > 0
        assert reason.retryable is True


def test_cli_and_mcp_can_render_same_spawn_service_outcome_models() -> None:
    cases = make_ctl_spawn_subagent_fixture_cases("inst_parent_conformance")

    for case in cases:
        cli_view = cli_spawn_outcome_view(case.outcome)
        mcp_view = mcp_spawn_outcome_view(case.outcome)

        if isinstance(case.outcome, SpawnSubagentAccepted):
            summary = launch_result_summary(case.outcome.launch_result)
            assert cli_view["status"] == "spawned"
            assert mcp_view["kind"] == "spawned"
            assert cli_view["instance_id"] == summary["instance_id"]
            assert mcp_view["instance_id"] == summary["instance_id"]
            assert cli_view["trace_id"] == summary["trace_id"]
            assert mcp_view["trace_id"] == summary["trace_id"]
        else:
            assert cli_view["status"] == "spawnability_error"
            assert mcp_view["kind"] == "spawnability_error"
            assert cli_view["reason"] == mcp_view["error"]["code"]


def test_regression_fixtures_lock_single_resolution_per_path() -> None:
    run_spawn_fixture = make_run_spawn_resolution_regression_fixture()
    mcp_fixture = make_mcp_control_resolution_regression_fixture()

    assert run_spawn_fixture.bug_path == "run_spawn"
    assert run_spawn_fixture.expected_resolution_calls == 1
    assert run_spawn_fixture.expected_runtime_resolution_calls == 1

    assert mcp_fixture.bug_path == "mcp_control"
    assert mcp_fixture.expected_resolution_calls == 1
    assert mcp_fixture.expected_runtime_resolution_calls == 1


def test_explicit_child_override_fixture_exists_for_spawn_contract_branch() -> None:
    override = override_persona_fixture_for_spawn()

    assert override.persona_ref == "personas/child-override.json"


def test_default_control_service_approval_resume_kill_paths_are_shared() -> None:
    class _StatePort:
        def __init__(self) -> None:
            self.state = make_ctl_spawn_subagent_fixture_cases("inst_local")[
                0
            ].outcome.launch_result.initial_state.model_copy(
                update={"state": "awaiting_approval"}
            )

        def read_state(self, instance_id: str):
            return Success(self.state)

        def load_contract(self, instance_id: str):
            fixture = make_ctl_spawn_subagent_fixture_cases("inst_local")[0]
            return Success(fixture.outcome.launch_result.resolved_contract)

    class _ApprovalPort:
        def __init__(self) -> None:
            self.writes: list[tuple[str, bool, str | None]] = []

        def write_approval_response(
            self, instance_id: str, approved: bool, note: str | None = None
        ):
            self.writes.append((instance_id, approved, note))
            return Success(None)

    class _LifecyclePort:
        def __init__(self) -> None:
            self.actions: list[tuple[str, str]] = []

        def approve(self, instance_id: str):
            self.actions.append(("approve", instance_id))
            return Success(None)

        def reject(self, instance_id: str, reason: str | None = None):
            self.actions.append(("reject", instance_id))
            return Success(None)

        def resume(self, instance_id: str):
            self.actions.append(("resume", instance_id))
            return Success(None)

        def kill(self, instance_id: str, reason: str | None = None):
            self.actions.append(("kill", instance_id))
            return Success(None)

    state_port = _StatePort()
    approval_port = _ApprovalPort()
    lifecycle_port = _LifecyclePort()
    service = DefaultControlService(
        contract_state_port=state_port,
        approval_port=approval_port,
        lifecycle_port=lifecycle_port,
    )

    approve_result = service.approve(
        ApprovalRequest(
            instance_id=state_port.state.instance_id, approved=True, note=None
        )
    )
    assert isinstance(approve_result, Success)
    assert approval_port.writes[-1] == (state_port.state.instance_id, True, None)
    assert lifecycle_port.actions[-1] == ("approve", state_port.state.instance_id)

    state_port.state = state_port.state.model_copy(update={"state": "suspended"})
    resume_result = service.resume(
        ResumeRequest(instance_id=state_port.state.instance_id)
    )
    assert isinstance(resume_result, Success)
    assert lifecycle_port.actions[-1] == ("resume", state_port.state.instance_id)

    state_port.state = state_port.state.model_copy(update={"state": "running"})
    kill_result = service.kill(KillRequest(instance_id=state_port.state.instance_id))
    assert isinstance(kill_result, Success)
    assert lifecycle_port.actions[-1] == ("kill", state_port.state.instance_id)


def test_default_control_service_spawn_uses_contract_spawnability_reason() -> None:
    fixture = make_ctl_spawn_subagent_fixture_cases("inst_contract_truth")[0]
    rejection_reason = SpawnabilityReason(
        code="inline_persona_requires_materialization",
        message="Inline persona must be materialized before child spawn.",
        user_action="Materialize parent persona snapshot first.",
        retryable=True,
    )
    contract = fixture.outcome.launch_result.resolved_contract.model_copy(
        update={
            "persona_provenance": fixture.outcome.launch_result.resolved_contract.persona_provenance.model_copy(
                update={
                    "spawnability": PersonaSpawnability(
                        can_spawn=False,
                        reason=rejection_reason,
                    )
                }
            )
        }
    )

    class _LaunchPort:
        def launch_subagent(self, request):
            raise AssertionError("launch must not be called when spawnability is false")

    class _PersonaPort(SpawnPersonaPort):
        def load_persona(self, persona_ref: str):
            raise AssertionError("persona load must not run when spawnability is false")

        def validate_parent_spawn_permission(
            self, *, parent_persona, child_persona_name: str
        ):
            return Success(None)

    service = DefaultControlService(
        contract_state_port=type(
            "_ContractStatePort",
            (),
            {
                "read_state": lambda self, instance_id: Success(
                    fixture.outcome.launch_result.initial_state.model_copy(
                        update={"instance_id": "inst_contract_truth"}
                    )
                ),
                "load_contract": lambda self, instance_id: Success(contract),
            },
        )(),
        approval_port=type(
            "_ApprovalPort",
            (),
            {
                "write_approval_response": lambda self, instance_id, approved, note=None: (
                    Success(None)
                )
            },
        )(),
        lifecycle_port=type(
            "_LifecyclePort",
            (),
            {
                "approve": lambda self, instance_id: Success(None),
                "reject": lambda self, instance_id, reason=None: Success(None),
                "resume": lambda self, instance_id: Success(None),
                "kill": lambda self, instance_id, reason=None: Success(None),
            },
        )(),
        launch_port=_LaunchPort(),
        spawn_persona_port=_PersonaPort(),
        generate_instance_id=lambda: "inst_child_contract_truth",
    )

    result = service.spawn_subagent(
        SpawnSubagentRequest(
            parent_instance_id="inst_contract_truth",
            task="child task",
        )
    )

    assert isinstance(result, Success)
    outcome = result.unwrap()
    assert isinstance(outcome, SpawnSubagentRejected)
    assert outcome.kind == "spawnability_error"
    assert outcome.reason.code == rejection_reason.code
