"""Step definitions for quality gates command injection tests."""

import importlib.util
import os
import types
from pathlib import Path
from unittest.mock import MagicMock, patch

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


_SCRIPT_PATH = (
    Path(__file__).parent.parent.parent / "scripts" / "check-quality-gates.py"
)


def _load_quality_gates_module() -> types.ModuleType:
    """Load the check-quality-gates.py script as a module via importlib."""
    spec = importlib.util.spec_from_file_location("check_quality_gates", _SCRIPT_PATH)
    if spec is None or spec.loader is None:
        raise ImportError(f"Cannot load module from {_SCRIPT_PATH}")
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


_qg = _load_quality_gates_module()


@given("the quality gates script is available")
def step_script_available(context: Context) -> None:
    """Verify the quality gates script is available."""
    script_path = Path("scripts/check-quality-gates.py")
    assert script_path.exists(), f"Script {script_path} does not exist"


@given("the project root is set correctly")
def step_project_root_set(context: Context) -> None:
    """Set up the project root context."""
    context.original_cwd = os.getcwd()


@when('I validate the path "{path_str}"')
def step_validate_path(context: Context, path_str: str) -> None:
    """Validate a path string."""
    context.path_str = path_str
    context.validation_error = None
    context.validated_path = None

    try:
        context.validated_path = _qg.validate_path(path_str)
    except ValueError as e:
        context.validation_error = str(e)


@when('I validate the path ""')
def step_validate_empty_path(context: Context) -> None:
    """Validate an empty path string."""
    step_validate_path(context, "")


@then("the path validation should succeed")
def step_validation_success(context: Context) -> None:
    """Assert that validation succeeded."""
    assert context.validation_error is None, (
        f"Expected validation to succeed but got: {context.validation_error}"
    )
    assert context.validated_path is not None, "Expected a validated path"


@then("the path validation should fail")
def step_validation_fail(context: Context) -> None:
    """Assert that validation failed."""
    assert context.validation_error is not None, (
        "Expected validation to fail but it succeeded"
    )


@then("the validated path should be absolute")
def step_path_is_absolute(context: Context) -> None:
    """Assert that the returned path is absolute."""
    assert context.validated_path is not None, "No validated path available"
    assert context.validated_path.is_absolute(), (
        f"Expected absolute path but got: {context.validated_path}"
    )


@then('the path validation error should contain "{text}"')
def step_error_contains_text(context: Context, text: str) -> None:
    """Assert that the path validation error message contains specific text."""
    assert context.validation_error is not None, "No error message available"
    assert text.lower() in context.validation_error.lower(), (
        f"Expected error to contain '{text}' but got: {context.validation_error}"
    )


@when("I run the security check")
def step_run_security_check(context: Context) -> None:
    """Run the security check function."""
    mock_result = MagicMock(returncode=0, stdout='{"results": []}', stderr="")
    with patch.object(_qg.subprocess, "run", return_value=mock_result) as mock_run:
        context.security_result = _qg.check_security()
        context.mock_run = mock_run


@when("I run the dead code check")
def step_run_dead_code_check(context: Context) -> None:
    """Run the dead code check function."""
    mock_result = MagicMock(returncode=0, stdout="", stderr="")
    with patch.object(_qg.subprocess, "run", return_value=mock_result) as mock_run:
        context.dead_code_result = _qg.check_dead_code()
        context.mock_run = mock_run


@when("I run the complexity check")
def step_run_complexity_check(context: Context) -> None:
    """Run the complexity check function."""
    mock_result = MagicMock(returncode=0, stdout="{}", stderr="")
    with patch.object(_qg.subprocess, "run", return_value=mock_result) as mock_run:
        context.complexity_result = _qg.check_complexity()
        context.mock_run = mock_run


@then("the subprocess should not be called with shell=True")
def step_subprocess_shell_not_true(context: Context) -> None:
    """Assert subprocess.run does not enable shell execution."""
    assert hasattr(context, "mock_run"), "No mock_run available"
    for call in context.mock_run.call_args_list:
        kwargs = (
            call.kwargs
            if hasattr(call, "kwargs")
            else (call[1] if len(call) > 1 else {})
        )
        assert kwargs.get("shell") is not True, (
            f"Expected shell not to be True but got: {kwargs.get('shell')}"
        )


@then("all path arguments should be validated")
def step_all_paths_validated(context: Context) -> None:
    """Assert that path arguments were resolved before subprocess execution."""
    assert hasattr(context, "mock_run"), "No mock_run available"
    assert context.mock_run.called, "subprocess.run was not called"
    path_like_args = []
    for call in context.mock_run.call_args_list:
        cmd = call.args[0] if call.args else call.kwargs.get("args", [])
        for arg in cmd:
            if not isinstance(arg, str):
                continue
            if "/" in arg or arg.endswith((".py", ".toml")):
                path_like_args.append(arg)

    assert path_like_args, "No path-like subprocess arguments were found"
    for arg in path_like_args:
        assert Path(arg).is_absolute(), f"Path argument was not validated: {arg}"
