"""Step definitions for resource_handler_base_coverage_r3.feature.

Covers uncovered lines in _base.py:
  - L132-134: resolve() RuntimeError when sandbox.context is None
  - L159-160: _require_location() ValueError for no location
  - L224-225: delete() NotImplementedError
  - L238-239: list_children() NotImplementedError
  - L252: diff() NotImplementedError
  - L264-265: discover_children() NotImplementedError
  - L305-307: create_sandbox() RuntimeError when sandbox.context is None
  - L376-382: project_access() ImportError fallback (local mode)
  - L384, L386-391: project_access() ValueError (invalid action)

All step text uses the ``rhbcov`` prefix to avoid collisions.
"""

from __future__ import annotations

import sys
from unittest.mock import MagicMock, patch

from behave import given, then, when
from behave.runner import Context

from cleveragents.domain.models.core.resource import (
    PhysVirt,
    Resource,
    ResourceCapabilities,
    SandboxStrategy,
)
from cleveragents.resource.handlers._base import BaseResourceHandler
from cleveragents.resource.handlers.database import DatabaseResourceHandler

__all__: list[str] = []


class _MinimalHandler(BaseResourceHandler):
    """Minimal handler that only implements required abstract methods."""

    _default_strategy = SandboxStrategy.OVERLAY
    _type_label = "test"

    def read(self, *, resource: object, path: str = "") -> object:
        raise NotImplementedError("test handler does not support read()")

    def write(self, *, resource: object, path: str = "", data: bytes = b"") -> object:
        raise NotImplementedError("test handler does not support write()")


_CB32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
_RHBCOV_COUNTER = 0


def _next_id() -> str:
    """Generate a valid 26-char Crockford Base32 ID for tests."""
    global _RHBCOV_COUNTER
    _RHBCOV_COUNTER += 1
    n = _RHBCOV_COUNTER + 0xBBC000
    chars: list[str] = []
    for _ in range(26):
        chars.append(_CB32[n % 32])
        n //= 32
    return "".join(reversed(chars))


def _make_resource(
    rtype: str, location: str | None, rid: str | None = None
) -> Resource:
    return Resource(
        resource_id=rid or _next_id(),
        resource_type_name=rtype,
        classification=PhysVirt.PHYSICAL,
        location=location,
        capabilities=ResourceCapabilities(
            readable=True, writable=True, sandboxable=True
        ),
    )


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


@given("rhbcov a database handler with a located resource")
def step_rhbcov_db_handler_located(context: Context) -> None:
    # Use minimal handler to test base class NotImplementedError defaults
    context.rhbcov_handler = _MinimalHandler()
    context.rhbcov_resource = _make_resource("postgres", "/tmp/rhbcov-fake-db")
    context.rhbcov_error = None
    context.rhbcov_result = None


@given("rhbcov a database handler with a locationless resource")
def step_rhbcov_db_handler_no_location(context: Context) -> None:
    context.rhbcov_handler = DatabaseResourceHandler()
    context.rhbcov_resource = _make_resource("postgres", None)
    context.rhbcov_error = None
    context.rhbcov_result = None


@given("rhbcov a mock sandbox manager returning context-less sandbox")
def step_rhbcov_mock_sandbox_mgr_no_context(context: Context) -> None:
    """Create a mock SandboxManager whose get_or_create_sandbox returns
    a sandbox with context=None, triggering the RuntimeError in resolve().
    """
    mock_sandbox = MagicMock()
    mock_sandbox.context = None
    mock_sandbox.sandbox_id = "mock-sandbox-001"

    mock_mgr = MagicMock()
    mock_mgr.get_or_create_sandbox.return_value = mock_sandbox

    context.rhbcov_sandbox_mgr = mock_mgr


@given(
    "rhbcov a mock sandbox manager returning no existing sandbox and context-less new sandbox"
)
def step_rhbcov_mock_sandbox_mgr_create_no_context(context: Context) -> None:
    """For create_sandbox: get_sandbox returns None (no existing sandbox),
    then get_or_create_sandbox returns a sandbox with context=None.
    """
    mock_sandbox = MagicMock()
    mock_sandbox.context = None
    mock_sandbox.sandbox_id = "mock-sandbox-002"

    mock_mgr = MagicMock()
    mock_mgr.get_sandbox.return_value = None
    mock_mgr.get_or_create_sandbox.return_value = mock_sandbox

    context.rhbcov_sandbox_mgr = mock_mgr


@given("rhbcov the permission service import is patched to raise ImportError")
def step_rhbcov_patch_permission_import_error(context: Context) -> None:
    """Patch sys.modules to make the permission_service import fail with
    ImportError inside project_access().
    """
    patcher = patch.dict(
        sys.modules,
        {
            "cleveragents.application.services.permission_service": None,
        },
    )
    patcher.start()
    context.add_cleanup(patcher.stop)


@given("rhbcov the permission service is patched to raise ValueError on action")
def step_rhbcov_patch_permission_value_error(context: Context) -> None:
    """Patch the permission service so it imports successfully but
    PermissionAction() raises ValueError for the invalid action string.

    We patch get_default_permission_service to return a mock service,
    and PermissionAction to raise ValueError when called.
    """
    # Create a mock module for permission_service
    mock_perm_svc_module = MagicMock()
    mock_perm_svc_module.get_default_permission_service.return_value = MagicMock()

    # Create a mock module for permission models where PermissionAction raises
    mock_perm_module = MagicMock()
    mock_perm_module.PermissionAction.side_effect = ValueError(
        "'invalid_action_xyz' is not a valid PermissionAction"
    )
    mock_perm_module.PermissionScope = MagicMock()

    patcher = patch.dict(
        sys.modules,
        {
            "cleveragents.application.services.permission_service": mock_perm_svc_module,
            "cleveragents.domain.models.core.permission": mock_perm_module,
        },
    )
    patcher.start()
    context.add_cleanup(patcher.stop)


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


@when("rhbcov I call resolve on the handler")
def step_rhbcov_call_resolve(context: Context) -> None:
    try:
        context.rhbcov_result = context.rhbcov_handler.resolve(
            resource=context.rhbcov_resource,
            plan_id=_next_id(),
            slot_name="test-slot",
            sandbox_manager=context.rhbcov_sandbox_mgr,
            access="read_only",
        )
    except Exception as exc:
        context.rhbcov_error = exc


@when("rhbcov I call _require_location on the handler")
def step_rhbcov_call_require_location(context: Context) -> None:
    try:
        context.rhbcov_result = context.rhbcov_handler._require_location(
            context.rhbcov_resource
        )
    except Exception as exc:
        context.rhbcov_error = exc


@when("rhbcov I call delete on the base handler")
def step_rhbcov_call_delete(context: Context) -> None:
    try:
        context.rhbcov_result = context.rhbcov_handler.delete(
            resource=context.rhbcov_resource, path="some/path"
        )
    except Exception as exc:
        context.rhbcov_error = exc


@when("rhbcov I call list_children on the base handler")
def step_rhbcov_call_list_children(context: Context) -> None:
    try:
        context.rhbcov_result = context.rhbcov_handler.list_children(
            resource=context.rhbcov_resource
        )
    except Exception as exc:
        context.rhbcov_error = exc


@when("rhbcov I call diff on the base handler")
def step_rhbcov_call_diff(context: Context) -> None:
    try:
        context.rhbcov_result = context.rhbcov_handler.diff(
            resource=context.rhbcov_resource, other_location="/tmp/other"
        )
    except Exception as exc:
        context.rhbcov_error = exc


@when("rhbcov I call discover_children on the base handler")
def step_rhbcov_call_discover_children(context: Context) -> None:
    try:
        context.rhbcov_result = context.rhbcov_handler.discover_children(
            resource=context.rhbcov_resource
        )
    except Exception as exc:
        context.rhbcov_error = exc


@when("rhbcov I call create_sandbox on the handler")
def step_rhbcov_call_create_sandbox(context: Context) -> None:
    try:
        context.rhbcov_result = context.rhbcov_handler.create_sandbox(
            resource=context.rhbcov_resource,
            plan_id=_next_id(),
            sandbox_manager=context.rhbcov_sandbox_mgr,
        )
    except Exception as exc:
        context.rhbcov_error = exc


@when('rhbcov I call project_access with principal "{principal}" and action "{action}"')
def step_rhbcov_call_project_access(
    context: Context, principal: str, action: str
) -> None:
    try:
        context.rhbcov_result = context.rhbcov_handler.project_access(
            resource=context.rhbcov_resource,
            principal=principal,
            action=action,
        )
    except Exception as exc:
        context.rhbcov_error = exc


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


@then('rhbcov a RuntimeError should be stored containing "{fragment}"')
def step_rhbcov_runtime_error(context: Context, fragment: str) -> None:
    assert context.rhbcov_error is not None, "Expected an error but none was raised"
    assert isinstance(context.rhbcov_error, RuntimeError), (
        f"Expected RuntimeError, got {type(context.rhbcov_error).__name__}: "
        f"{context.rhbcov_error}"
    )
    assert fragment in str(context.rhbcov_error), (
        f"'{fragment}' not found in error message: '{context.rhbcov_error}'"
    )


@then('rhbcov a ValueError should be stored containing "{fragment}"')
def step_rhbcov_value_error(context: Context, fragment: str) -> None:
    assert context.rhbcov_error is not None, "Expected an error but none was raised"
    assert isinstance(context.rhbcov_error, ValueError), (
        f"Expected ValueError, got {type(context.rhbcov_error).__name__}: "
        f"{context.rhbcov_error}"
    )
    assert fragment in str(context.rhbcov_error), (
        f"'{fragment}' not found in error message: '{context.rhbcov_error}'"
    )


@then('rhbcov a NotImplementedError should be stored containing "{fragment}"')
def step_rhbcov_not_implemented_error(context: Context, fragment: str) -> None:
    assert context.rhbcov_error is not None, "Expected an error but none was raised"
    assert isinstance(context.rhbcov_error, NotImplementedError), (
        f"Expected NotImplementedError, got "
        f"{type(context.rhbcov_error).__name__}: {context.rhbcov_error}"
    )
    assert fragment in str(context.rhbcov_error), (
        f"'{fragment}' not found in error message: '{context.rhbcov_error}'"
    )


@then("rhbcov the access result should be permitted")
def step_rhbcov_access_permitted(context: Context) -> None:
    assert context.rhbcov_error is None, f"Unexpected error: {context.rhbcov_error}"
    assert context.rhbcov_result is not None, "Expected an AccessResult, got None"
    assert context.rhbcov_result.permitted is True, (
        f"Expected permitted=True, got {context.rhbcov_result.permitted}"
    )


@then("rhbcov the access result should not be permitted")
def step_rhbcov_access_not_permitted(context: Context) -> None:
    assert context.rhbcov_error is None, f"Unexpected error: {context.rhbcov_error}"
    assert context.rhbcov_result is not None, "Expected an AccessResult, got None"
    assert context.rhbcov_result.permitted is False, (
        f"Expected permitted=False, got {context.rhbcov_result.permitted}"
    )


@then('rhbcov the access result reason should contain "{fragment}"')
def step_rhbcov_access_reason(context: Context, fragment: str) -> None:
    assert context.rhbcov_result is not None, "Expected an AccessResult, got None"
    assert fragment in context.rhbcov_result.reason, (
        f"'{fragment}' not found in reason: '{context.rhbcov_result.reason}'"
    )
