"""Step definitions for resource_type_bootstrap_fs.feature.

These tests target bug #523.  The first scenario (without bootstrap) is a
genuine TDD failing test: it creates a fresh registry *without* calling
``bootstrap_builtin_types()`` and asserts ``fs-directory`` 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
from behave.runner import Context
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.

    When *run_bootstrap* is ``True`` the built-in types are seeded; when
    ``False`` the registry is left empty (reproducing the bug).
    """
    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:
        service.bootstrap_builtin_types()
    context.bootstrap_fs_service = service
    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:
            # Defensive: currently resource_add raises typer.Abort (a
            # RuntimeError subclass) on failure, not SystemExit.  This
            # handler guards against future changes where the function
            # is invoked through the Typer CLI runner, which converts
            # exceptions into SystemExit.
            if exc.code not in (None, 0):
                failed = True
                failure_reason = f"SystemExit: {exc.code}"
        except Exception as exc:
            failed = True
            # typer.Abort() carries no message; str(exc) is empty.
            # Fall back to the class name so failure_reason is not blank.
            reason = str(exc) if str(exc) else "(no message)"
            failure_reason = f"{type(exc).__name__}: {reason}"

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


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


@given("a 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_fs_output = ""
    context.bootstrap_fs_failed = False


@given("a 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 #523: the registry is empty because
    ``bootstrap_builtin_types()`` is never called during initialization.
    """
    _make_service(context, run_bootstrap=False)
    context.bootstrap_fs_output = ""
    context.bootstrap_fs_failed = False


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


@when('I query the fs bootstrap 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_fs_service
    try:
        context.bootstrap_fs_type_spec = service.show_type(name)
        context.bootstrap_fs_type_found = True
    except NotFoundError:
        context.bootstrap_fs_type_spec = None
        context.bootstrap_fs_type_found = False


@when('I run resource add for type "{type_name}" named "{name}" with path "{path}"')
def step_run_resource_add_fs(
    context: Context, type_name: str, name: str, path: 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_fs_service,
    ):
        output, failed = _capture_output(
            resource_add,
            type_name=type_name,
            name=name,
            path=path,
            branch=None,
            description=None,
            image=None,
            clone_into=None,
            update=False,
            read_only=False,
            fmt="rich",
        )
    context.bootstrap_fs_output = output
    context.bootstrap_fs_failed = failed


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


@then('the bootstrap fs 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_fs_type_found is True, (
        f"Expected resource type '{name}' to exist in the registry after "
        f"bootstrap, but it was not found."
    )


@then('the bootstrap fs resource type kind should be "{kind}"')
def step_type_kind(context: Context, kind: str) -> None:
    """Assert the resource type has the expected kind."""
    spec = context.bootstrap_fs_type_spec
    actual = spec.resource_kind
    actual_str = actual.value if hasattr(actual, "value") else str(actual)
    assert actual_str == kind, f"Expected kind '{kind}', got '{actual_str}'"


@then('the bootstrap fs resource type sandbox_strategy should be "{strategy}"')
def step_type_sandbox(context: Context, strategy: str) -> None:
    """Assert the resource type has the expected sandbox strategy."""
    spec = context.bootstrap_fs_type_spec
    actual = spec.sandbox_strategy
    actual_str = actual.value if hasattr(actual, "value") else str(actual)
    assert actual_str == strategy, (
        f"Expected sandbox_strategy '{strategy}', got '{actual_str}'"
    )


@then("the 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_fs_failed is False, (
        f"Expected 'resource add' to succeed, but it failed. "
        f"Output: {context.bootstrap_fs_output!r}"
    )


@then('the resource add output should contain "{text}"')
def step_fs_output_should_contain(context: Context, text: str) -> None:
    """Assert the output contains the given text."""
    assert text in context.bootstrap_fs_output, (
        f"Expected output to contain '{text}', but it did not. "
        f"Full output: {context.bootstrap_fs_output!r}"
    )


@then('the 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_fs_output, (
        f"Output unexpectedly contained '{text}'. "
        f"Full output: {context.bootstrap_fs_output!r}"
    )


@then("the bootstrap fs resource type user_addable should be true")
def step_type_user_addable_true(context: Context) -> None:
    """Assert the resource type is user-addable."""
    spec = context.bootstrap_fs_type_spec
    assert spec.user_addable is True, (
        f"Expected user_addable to be True, got {spec.user_addable!r}"
    )


@then('the bootstrap fs resource type child_types should contain "{child}"')
def step_type_child_types_contain(context: Context, child: str) -> None:
    """Assert the resource type has the given child type."""
    spec = context.bootstrap_fs_type_spec
    child_types = spec.child_types or []
    assert child in child_types, (
        f"Expected child_types to contain '{child}', got {child_types!r}"
    )


@then("the bootstrap fs resource type parent_types should be empty")
def step_type_parent_types_empty(context: Context) -> None:
    """Assert the resource type has no parent types."""
    spec = context.bootstrap_fs_type_spec
    parent_types = spec.parent_types or []
    assert len(parent_types) == 0, (
        f"Expected parent_types to be empty, got {parent_types!r}"
    )


@then('the bootstrap fs resource type parent_types should contain "{parent}"')
def step_type_parent_types_contain(context: Context, parent: str) -> None:
    """Assert the resource type has the given parent type."""
    spec = context.bootstrap_fs_type_spec
    parent_types = spec.parent_types or []
    assert parent in parent_types, (
        f"Expected parent_types to contain '{parent}', got {parent_types!r}"
    )
