"""Step definitions for checkpoint_cli_commands.feature."""

from __future__ import annotations

from datetime import UTC, datetime
from unittest.mock import MagicMock, patch

from behave import given, then, when
from behave.runner import Context
from typer.testing import CliRunner

from cleveragents.cli.commands.plan import app as plan_app
from cleveragents.core.exceptions import CleverAgentsError, ResourceNotFoundError
from cleveragents.domain.models.core.checkpoint import Checkpoint, CheckpointMetadata

_PLAN_ID = "01ARZ3NDEKTSV4RRFFQ69G5FAV"
_CP_ID_1 = "01ARZ3NDEKTSV4RRFFQ69G5FB1"
_CP_ID_2 = "01ARZ3NDEKTSV4RRFFQ69G5FB2"
_CP_ID_3 = "01ARZ3NDEKTSV4RRFFQ69G5FB3"

_PATCH_CONTAINER = "cleveragents.application.container.get_container"


def _make_checkpoint(
    checkpoint_id: str = _CP_ID_1,
    plan_id: str = _PLAN_ID,
    checkpoint_type: str = "manual",
    reason: str = "test checkpoint",
    phase: str = "execute",
    created_at: datetime | None = None,
) -> Checkpoint:
    """Build a Checkpoint domain object for testing."""
    return Checkpoint(
        checkpoint_id=checkpoint_id,
        plan_id=plan_id,
        sandbox_ref="abc123",
        checkpoint_type=checkpoint_type,
        created_at=created_at or datetime(2026, 4, 1, 10, 0, 0, tzinfo=UTC),
        metadata=CheckpointMetadata(
            reason=reason,
            source_tool="test",
            phase=phase,
        ),
    )


# ----------------------------------------------------------------------
# Given steps
# ----------------------------------------------------------------------


@given('a cp-cli mocked checkpoint service with no checkpoints for plan "{plan_id}"')
def step_cp_cli_no_checkpoints(context: Context, plan_id: str) -> None:
    """Set up a checkpoint service that returns no checkpoints."""
    context.cp_cli_runner = CliRunner()
    mock_container = MagicMock()
    mock_svc = MagicMock()
    mock_svc.list_checkpoints.return_value = []
    mock_container.checkpoint_service.return_value = mock_svc
    context.cp_cli_mock_container = mock_container


@given(
    'a cp-cli mocked checkpoint service with {count:d} checkpoints for plan "{plan_id}"'
)
def step_cp_cli_n_checkpoints(context: Context, count: int, plan_id: str) -> None:
    """Set up a checkpoint service that returns N checkpoints."""
    context.cp_cli_runner = CliRunner()
    checkpoints = [
        _make_checkpoint(
            checkpoint_id=f"01ARZ3NDEKTSV4RRFFQ69G5F{i:02d}",
            plan_id=plan_id,
            created_at=datetime(2026, 4, 1, 10, i, 0, tzinfo=UTC),
        )
        for i in range(1, count + 1)
    ]
    mock_container = MagicMock()
    mock_svc = MagicMock()
    mock_svc.list_checkpoints.return_value = checkpoints
    mock_container.checkpoint_service.return_value = mock_svc
    context.cp_cli_mock_container = mock_container


@given(
    'a cp-cli mocked checkpoint service with checkpoints of mixed types for plan "{plan_id}"'
)
def step_cp_cli_mixed_type_checkpoints(context: Context, plan_id: str) -> None:
    """Set up a checkpoint service with checkpoints of different types."""
    context.cp_cli_runner = CliRunner()
    checkpoints = [
        _make_checkpoint(
            checkpoint_id=_CP_ID_1, plan_id=plan_id, checkpoint_type="manual"
        ),
        _make_checkpoint(
            checkpoint_id=_CP_ID_2, plan_id=plan_id, checkpoint_type="pre_write"
        ),
        _make_checkpoint(
            checkpoint_id=_CP_ID_3, plan_id=plan_id, checkpoint_type="post_step"
        ),
    ]
    mock_container = MagicMock()
    mock_svc = MagicMock()
    mock_svc.list_checkpoints.return_value = checkpoints
    mock_container.checkpoint_service.return_value = mock_svc
    context.cp_cli_mock_container = mock_container


@given("a cp-cli mocked checkpoint service that raises CleverAgentsError on list")
def step_cp_cli_list_error(context: Context) -> None:
    """Set up a checkpoint service that raises CleverAgentsError on list."""
    context.cp_cli_runner = CliRunner()
    mock_container = MagicMock()
    mock_svc = MagicMock()
    mock_svc.list_checkpoints.side_effect = CleverAgentsError("service error")
    mock_container.checkpoint_service.return_value = mock_svc
    context.cp_cli_mock_container = mock_container


@given("a cp-cli mocked checkpoint service that can delete checkpoints")
def step_cp_cli_delete_ok(context: Context) -> None:
    """Set up a checkpoint service that successfully deletes checkpoints."""
    context.cp_cli_runner = CliRunner()
    mock_container = MagicMock()
    mock_svc = MagicMock()
    mock_svc.delete_checkpoint.return_value = None
    mock_container.checkpoint_service.return_value = mock_svc
    context.cp_cli_mock_container = mock_container


@given("a cp-cli mocked checkpoint service that raises ResourceNotFoundError on delete")
def step_cp_cli_delete_not_found(context: Context) -> None:
    """Set up a checkpoint service that raises ResourceNotFoundError on delete."""
    context.cp_cli_runner = CliRunner()
    mock_container = MagicMock()
    mock_svc = MagicMock()
    mock_svc.delete_checkpoint.side_effect = ResourceNotFoundError(
        resource_type="checkpoint", resource_id=_CP_ID_1
    )
    mock_container.checkpoint_service.return_value = mock_svc
    context.cp_cli_mock_container = mock_container


# ----------------------------------------------------------------------
# When steps
# ----------------------------------------------------------------------


@when('I invoke cp-cli checkpoint-list for plan "{plan_id}" in default format')
def step_cp_cli_invoke_list(context: Context, plan_id: str) -> None:
    """Invoke checkpoint-list command in default rich format."""
    with patch(_PATCH_CONTAINER, return_value=context.cp_cli_mock_container):
        context.cp_cli_result = context.cp_cli_runner.invoke(
            plan_app, ["checkpoint-list", plan_id]
        )


@when('I invoke cp-cli checkpoint-list for plan "{plan_id}" with format "{fmt}"')
def step_cp_cli_invoke_list_fmt(context: Context, plan_id: str, fmt: str) -> None:
    """Invoke checkpoint-list command with a specific output format."""
    with patch(_PATCH_CONTAINER, return_value=context.cp_cli_mock_container):
        context.cp_cli_result = context.cp_cli_runner.invoke(
            plan_app, ["checkpoint-list", plan_id, "--format", fmt]
        )


@when('I invoke cp-cli checkpoint-list for plan "{plan_id}" with sort "{sort}"')
def step_cp_cli_invoke_list_sort(context: Context, plan_id: str, sort: str) -> None:
    """Invoke checkpoint-list command with a sort option."""
    with patch(_PATCH_CONTAINER, return_value=context.cp_cli_mock_container):
        context.cp_cli_result = context.cp_cli_runner.invoke(
            plan_app, ["checkpoint-list", plan_id, "--sort", sort]
        )


@when(
    'I invoke cp-cli checkpoint-list for plan "{plan_id}" with type filter "{cp_type}"'
)
def step_cp_cli_invoke_list_type(context: Context, plan_id: str, cp_type: str) -> None:
    """Invoke checkpoint-list command with a type filter."""
    with patch(_PATCH_CONTAINER, return_value=context.cp_cli_mock_container):
        context.cp_cli_result = context.cp_cli_runner.invoke(
            plan_app, ["checkpoint-list", plan_id, "--type", cp_type]
        )


@when('I invoke cp-cli checkpoint-delete "{cp_id}" with yes flag')
def step_cp_cli_invoke_delete_yes(context: Context, cp_id: str) -> None:
    """Invoke checkpoint-delete command with --yes flag."""
    with patch(_PATCH_CONTAINER, return_value=context.cp_cli_mock_container):
        context.cp_cli_result = context.cp_cli_runner.invoke(
            plan_app, ["checkpoint-delete", cp_id, "--yes"]
        )


@when('I invoke cp-cli checkpoint-delete "{cp_id}" with yes flag and format "{fmt}"')
def step_cp_cli_invoke_delete_yes_fmt(context: Context, cp_id: str, fmt: str) -> None:
    """Invoke checkpoint-delete command with --yes flag and format."""
    with patch(_PATCH_CONTAINER, return_value=context.cp_cli_mock_container):
        context.cp_cli_result = context.cp_cli_runner.invoke(
            plan_app, ["checkpoint-delete", cp_id, "--yes", "--format", fmt]
        )


@when("I invoke cp-cli checkpoint-delete multiple IDs with yes flag")
def step_cp_cli_invoke_delete_multiple(context: Context) -> None:
    """Invoke checkpoint-delete command with multiple checkpoint IDs."""
    with patch(_PATCH_CONTAINER, return_value=context.cp_cli_mock_container):
        context.cp_cli_result = context.cp_cli_runner.invoke(
            plan_app, ["checkpoint-delete", _CP_ID_1, _CP_ID_2, "--yes"]
        )


@when("I invoke cp-cli checkpoint-delete with no IDs")
def step_cp_cli_invoke_delete_no_ids(context: Context) -> None:
    """Invoke checkpoint-delete command with no checkpoint IDs."""
    with patch(_PATCH_CONTAINER, return_value=context.cp_cli_mock_container):
        context.cp_cli_result = context.cp_cli_runner.invoke(
            plan_app, ["checkpoint-delete"]
        )


@when('I invoke cp-cli checkpoint-delete "{cp_id}" and decline confirmation')
def step_cp_cli_invoke_delete_decline(context: Context, cp_id: str) -> None:
    """Invoke checkpoint-delete command and decline the confirmation prompt."""
    with patch(_PATCH_CONTAINER, return_value=context.cp_cli_mock_container):
        context.cp_cli_result = context.cp_cli_runner.invoke(
            plan_app, ["checkpoint-delete", cp_id], input="n\n"
        )


# ----------------------------------------------------------------------
# Then steps
# ----------------------------------------------------------------------


@then("the cp-cli command should succeed")
def step_cp_cli_assert_success(context: Context) -> None:
    """Assert the CLI exited with code 0."""
    assert context.cp_cli_result.exit_code == 0, (
        f"Expected exit_code=0, got {context.cp_cli_result.exit_code}. "
        f"Output: {context.cp_cli_result.output}"
    )


@then("the cp-cli command should abort")
def step_cp_cli_assert_abort(context: Context) -> None:
    """Assert the CLI exited with a non-zero code."""
    assert context.cp_cli_result.exit_code != 0, (
        f"Expected non-zero exit_code, got {context.cp_cli_result.exit_code}. "
        f"Output: {context.cp_cli_result.output}"
    )


@then('the cp-cli output should contain "{text}"')
def step_cp_cli_assert_output_contains(context: Context, text: str) -> None:
    """Assert the CLI output contains the expected text."""
    assert text in context.cp_cli_result.output, (
        f"Expected output to contain {text!r}. "
        f"Actual output: {context.cp_cli_result.output}"
    )
