"""Step definitions for resource_type_bootstrap_git.feature.

These tests target bug #524.  The first scenario (without bootstrap) is a
genuine TDD failing test: it creates a fresh registry *without* calling
``bootstrap_builtin_types()`` and asserts ``git-checkout`` exists — which
fails until the init path is fixed to call bootstrap.

The remaining scenarios verify that ``bootstrap_builtin_types()`` itself
seeds the correct data and that the CLI ``resource add`` path works once
the types are present.
"""

from __future__ import annotations

from io import StringIO
from typing import Any
from unittest.mock import patch

from behave import given, then, when  # type: ignore[import-untyped]
from behave.runner import Context  # type: ignore[import-untyped]
from rich.console import Console
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from cleveragents.application.services.resource_registry_service import (
    ResourceRegistryService,
)
from cleveragents.core.exceptions import NotFoundError
from cleveragents.infrastructure.database.models import Base

# ── Helpers ──────────────────────────────────────────────────

_PATCH_SERVICE = "cleveragents.cli.commands.resource._get_registry_service"
_PATCH_CONSOLE = "cleveragents.cli.commands.resource.console"


def _make_service(context: Context, *, run_bootstrap: bool) -> ResourceRegistryService:
    """Create an in-memory ResourceRegistryService.

    Built-in types are now auto-bootstrapped in ``__init__``.  When
    *run_bootstrap* is ``True`` the step records the list of registered
    type names for assertion steps; when ``False`` the service is still
    created (and auto-bootstrapped) but no list is recorded.
    """
    engine = create_engine("sqlite:///:memory:", echo=False)
    Base.metadata.create_all(engine)
    factory = sessionmaker(bind=engine, expire_on_commit=False)
    service = ResourceRegistryService(session_factory=factory)
    if run_bootstrap:
        # Types are auto-bootstrapped in __init__; record them for assertions.
        context.bootstrap_git_registered = [  # type: ignore[attr-defined]
            t.name for t in service.list_types()
        ]
    context.bootstrap_git_service = service  # type: ignore[attr-defined]
    return service


def _capture_output(func: Any, *args: Any, **kwargs: Any) -> tuple[str, bool]:
    """Run a CLI function capturing its console output and success status."""
    buf = StringIO()
    console = Console(
        file=buf, width=200, no_color=True, highlight=False, force_terminal=False
    )

    failed = False
    failure_reason = ""
    with patch(_PATCH_CONSOLE, console):
        try:
            func(*args, **kwargs)
        except SystemExit as exc:
            if exc.code not in (None, 0):
                failed = True
                failure_reason = f"SystemExit: {exc.code}"
        except Exception as exc:
            failed = True
            failure_reason = f"{type(exc).__name__}: {exc}"

    output = buf.getvalue()
    if failure_reason:
        output = output + "\n" + failure_reason
    return output, failed


# ── Given steps ──────────────────────────────────────────────


@given("a bootstrap-git fresh in-memory resource registry with bootstrap")
def step_fresh_registry_with_bootstrap(context: Context) -> None:
    """Set up a fresh in-memory database and run bootstrap_builtin_types."""
    _make_service(context, run_bootstrap=True)
    context.bootstrap_git_output = ""  # type: ignore[attr-defined]
    context.bootstrap_git_failed = False  # type: ignore[attr-defined]


@given("a bootstrap-git fresh in-memory resource registry without bootstrap")
def step_fresh_registry_without_bootstrap(context: Context) -> None:
    """Set up a fresh in-memory database WITHOUT running bootstrap.

    This reproduces bug #524: the registry is empty because
    ``bootstrap_builtin_types()`` is never called during initialization.
    """
    _make_service(context, run_bootstrap=False)
    context.bootstrap_git_output = ""  # type: ignore[attr-defined]
    context.bootstrap_git_failed = False  # type: ignore[attr-defined]


# ── When steps ───────────────────────────────────────────────


@when('I query the bootstrap-git resource type registry for "{name}"')
def step_query_registry_for_type(context: Context, name: str) -> None:
    """Query the registry for a specific resource type by name."""
    service: ResourceRegistryService = context.bootstrap_git_service  # type: ignore[attr-defined]
    try:
        context.bootstrap_git_type_spec = service.show_type(name)  # type: ignore[attr-defined]
        context.bootstrap_git_type_found = True  # type: ignore[attr-defined]
    except NotFoundError:
        context.bootstrap_git_type_spec = None  # type: ignore[attr-defined]
        context.bootstrap_git_type_found = False  # type: ignore[attr-defined]


@when(
    'I run bootstrap-git resource add for type "{type_name}" named "{name}" with path "{path}" and branch "{branch}"'
)
def step_run_resource_add_git(
    context: Context, type_name: str, name: str, path: str, branch: str
) -> None:
    """Run resource add command via the CLI function."""
    from cleveragents.cli.commands.resource import resource_add

    with patch(
        _PATCH_SERVICE,
        return_value=context.bootstrap_git_service,  # type: ignore[attr-defined]
    ):
        output, failed = _capture_output(
            resource_add,
            type_name=type_name,
            name=name,
            path=path,
            branch=branch,
            description=None,
            image=None,
            clone_into=None,
            update=False,
            read_only=False,
            fmt="rich",
        )
    context.bootstrap_git_output = output  # type: ignore[attr-defined]
    context.bootstrap_git_failed = failed  # type: ignore[attr-defined]


# ── Then steps ───────────────────────────────────────────────


@then('the bootstrap-git resource type "{name}" should exist')
def step_type_should_exist(context: Context, name: str) -> None:
    """Assert the resource type was found in the registry."""
    assert context.bootstrap_git_type_found is True, (  # type: ignore[attr-defined]
        f"Expected resource type '{name}' to exist in the registry after "
        f"bootstrap, but it was not found. This is bug #524: "
        f"bootstrap_builtin_types() is never called during initialization."
    )


@then('the bootstrap-git resource type "{name}" should have kind "{kind}"')
def step_type_kind(context: Context, name: str, kind: str) -> None:
    """Assert the resource type has the expected kind."""
    spec = context.bootstrap_git_type_spec  # type: ignore[attr-defined]
    actual = spec.resource_kind.value
    assert actual == kind, f"Expected kind '{kind}', got '{actual}'"


@then(
    'the bootstrap-git resource type "{name}" should have sandbox_strategy "{strategy}"'
)
def step_type_sandbox(context: Context, name: str, strategy: str) -> None:
    """Assert the resource type has the expected sandbox strategy."""
    spec = context.bootstrap_git_type_spec  # type: ignore[attr-defined]
    actual = spec.sandbox_strategy.value
    assert actual == strategy, f"Expected sandbox_strategy '{strategy}', got '{actual}'"


@then('the bootstrap-git resource type "{name}" should be user_addable')
def step_type_user_addable(context: Context, name: str) -> None:
    """Assert the resource type is user-addable."""
    spec = context.bootstrap_git_type_spec  # type: ignore[attr-defined]
    assert spec.user_addable is True, (
        f"Expected user_addable=True, got {spec.user_addable}"
    )


@then('the bootstrap-git registered types should include "{name}"')
def step_registered_includes(context: Context, name: str) -> None:
    """Assert the bootstrap return value includes the named type."""
    registered = context.bootstrap_git_registered  # type: ignore[attr-defined]
    assert name in registered, (
        f"Expected '{name}' in bootstrap_builtin_types() return value, "
        f"got: {registered}"
    )


@then("the bootstrap-git resource add command should succeed")
def step_resource_add_should_succeed(context: Context) -> None:
    """Assert the resource add command did not fail."""
    assert context.bootstrap_git_failed is not True, (  # type: ignore[attr-defined]
        f"Expected 'resource add' to succeed, but it failed. "
        f"Output: {context.bootstrap_git_output!r}. "
        f"This is bug #524: bootstrap_builtin_types() is never called."
    )


@then('the bootstrap-git resource add output should not contain "{text}"')
def step_output_should_not_contain(context: Context, text: str) -> None:
    """Assert the output does not contain the given text."""
    assert text not in context.bootstrap_git_output, (  # type: ignore[attr-defined]
        f"Output unexpectedly contained '{text}'. "
        f"Full output: {context.bootstrap_git_output!r}"
    )
