"""Step definitions for validation attach type-guard tests.

Verifies that ``agents validation attach`` rejects plain Tools and only
accepts entries whose ``tool_type`` discriminator is ``"validation"``.
"""

from __future__ import annotations

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.validation import app as validation_app
from cleveragents.core.exceptions import ToolTypeMismatchError

_ATTACHMENT_ULID = "01TYPETEST0000000000000001"


# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------


@given("a validation attach type guard test runner")
def step_type_guard_runner(context: Context) -> None:
    """Set up the CLI runner for type-guard tests."""
    context.runner = CliRunner()


@given("a validation attach type guard mocked environment")
def step_type_guard_mock_env(context: Context) -> None:
    """Set up the mocked service for type-guard tests."""
    context.mock_tool_registry_service = MagicMock()

    context.validation_patcher = patch(
        "cleveragents.cli.commands.validation._get_tool_registry_service",
        return_value=context.mock_tool_registry_service,
    )
    context.validation_patcher.start()
    context.add_cleanup(context.validation_patcher.stop)

    context.last_result = None


# ---------------------------------------------------------------------------
# Given: plain Tool (domain object)
# ---------------------------------------------------------------------------


@given('a plain tool "{name}" is registered with tool_type "tool"')
def step_plain_tool_registered(context: Context, name: str) -> None:
    """Configure the mock service to raise ToolTypeMismatchError for a plain Tool."""
    context.mock_tool_registry_service.attach_validation.side_effect = (
        ToolTypeMismatchError(tool_name=name, actual_type="tool")
    )


# ---------------------------------------------------------------------------
# Given: plain Tool (dict representation)
# ---------------------------------------------------------------------------


@given('a plain tool dict "{name}" is registered with tool_type "tool"')
def step_plain_tool_dict_registered(context: Context, name: str) -> None:
    """Configure the mock service to raise ToolTypeMismatchError for a plain Tool dict."""
    context.mock_tool_registry_service.attach_validation.side_effect = (
        ToolTypeMismatchError(tool_name=name, actual_type="tool")
    )


# ---------------------------------------------------------------------------
# Given: genuine Validation (domain object)
# ---------------------------------------------------------------------------


@given('a genuine validation "{name}" is registered with tool_type "validation"')
def step_genuine_validation_registered(context: Context, name: str) -> None:
    """Configure the mock service to return a successful attachment for a Validation."""
    mock_attachment = MagicMock()
    mock_attachment.attachment_id = _ATTACHMENT_ULID
    mock_attachment.validation_name = name
    mock_attachment.resource_id = "git-checkout/my-repo"
    mock_attachment.mode = "required"
    mock_attachment.project_name = None
    mock_attachment.plan_id = None
    mock_attachment.created_at = "2026-01-01T00:00:00"
    context.mock_tool_registry_service.attach_validation.return_value = mock_attachment
    context.mock_tool_registry_service.attach_validation.side_effect = None


# ---------------------------------------------------------------------------
# Given: genuine Validation (dict representation)
# ---------------------------------------------------------------------------


@given('a genuine validation dict "{name}" is registered with tool_type "validation"')
def step_genuine_validation_dict_registered(context: Context, name: str) -> None:
    """Configure the mock service to return a successful attachment for a Validation dict."""
    mock_attachment = MagicMock()
    mock_attachment.attachment_id = _ATTACHMENT_ULID
    mock_attachment.validation_name = name
    mock_attachment.resource_id = "git-checkout/my-repo"
    mock_attachment.mode = "required"
    mock_attachment.project_name = None
    mock_attachment.plan_id = None
    mock_attachment.created_at = "2026-01-01T00:00:00"
    context.mock_tool_registry_service.attach_validation.return_value = mock_attachment
    context.mock_tool_registry_service.attach_validation.side_effect = None


# ---------------------------------------------------------------------------
# When
# ---------------------------------------------------------------------------


@when('I invoke validation attach "{name}" to "{resource}"')
def step_invoke_validation_attach(context: Context, name: str, resource: str) -> None:
    """Invoke the validation attach CLI command."""
    result = context.runner.invoke(
        validation_app,
        ["attach", resource, name, "--format", "plain"],
    )
    context.last_result = result


@when(
    'I invoke validation attach with named option "{option}" "{value}" '
    'for "{name}" to "{resource}"'
)
def step_invoke_validation_attach_named_option(
    context: Context, option: str, value: str, name: str, resource: str
) -> None:
    """Invoke the validation attach CLI command with a single named option."""
    result = context.runner.invoke(
        validation_app,
        ["attach", resource, name, "--format", "plain", option, value],
    )
    context.last_result = result


@when(
    'I invoke validation attach with named options "{option1}" "{value1}" '
    '"{option2}" "{value2}" for "{name}" to "{resource}"'
)
def step_invoke_validation_attach_named_options(
    context: Context,
    option1: str,
    value1: str,
    option2: str,
    value2: str,
    name: str,
    resource: str,
) -> None:
    """Invoke the validation attach CLI command with multiple named options."""
    result = context.runner.invoke(
        validation_app,
        [
            "attach",
            resource,
            name,
            "--format",
            "plain",
            option1,
            value1,
            option2,
            value2,
        ],
    )
    context.last_result = result


@when(
    'I invoke validation attach with positional arg "{arg}" '
    'for "{name}" to "{resource}"'
)
def step_invoke_validation_attach_positional_arg(
    context: Context, arg: str, name: str, resource: str
) -> None:
    """Invoke the validation attach CLI command with a positional key=value arg."""
    result = context.runner.invoke(
        validation_app,
        ["attach", resource, name, "--format", "plain", arg],
    )
    context.last_result = result


@when(
    'I invoke validation attach with dangling option "{option}" '
    'for "{name}" to "{resource}"'
)
def step_invoke_validation_attach_dangling_option(
    context: Context, option: str, name: str, resource: str
) -> None:
    """Invoke the validation attach CLI command with a named option missing its value."""
    result = context.runner.invoke(
        validation_app,
        ["attach", resource, name, "--format", "plain", option],
    )
    context.last_result = result


# ---------------------------------------------------------------------------
# Then: rejection
# ---------------------------------------------------------------------------


@then("the validation attach should be rejected")
def step_attach_rejected(context: Context) -> None:
    """Verify the attach command exited with a non-zero code (Abort)."""
    assert context.last_result is not None
    assert context.last_result.exit_code != 0, (
        f"Expected non-zero exit code (rejection), got "
        f"{context.last_result.exit_code}. "
        f"Output: {context.last_result.output}"
    )


@then('the rejection output should contain "{text}"')
def step_rejection_output_contains(context: Context, text: str) -> None:
    """Verify the rejection output contains the expected text."""
    output = context.last_result.output
    assert text in output, f"Expected '{text}' in rejection output, got: {output!r}"


# ---------------------------------------------------------------------------
# Then: success
# ---------------------------------------------------------------------------


@then("the validation attach should succeed")
def step_attach_succeeds(context: Context) -> None:
    """Verify the attach command exited with code 0."""
    assert context.last_result is not None
    assert context.last_result.exit_code == 0, (
        f"Expected exit 0 (success), got {context.last_result.exit_code}. "
        f"Output: {context.last_result.output}"
    )
