"""Step definitions for A2A module imports audit Behave scenarios.

Verifies that all ACP module imports have been renamed to A2A per ADR-047,
and that the A2A module is properly structured and exportable.
"""

from __future__ import annotations

import importlib
import re
from pathlib import Path

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

use_step_matcher("parse")

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

_REPO_ROOT = Path(__file__).parent.parent.parent


def _src_root() -> Path:
    return _REPO_ROOT / "src" / "cleveragents"


def _a2a_root() -> Path:
    return _src_root() / "a2a"


# ---------------------------------------------------------------------------
# Background steps
# ---------------------------------------------------------------------------


@given("the cleveragents-core repository is initialized")
def step_repo_initialized(context: Context) -> None:
    assert _REPO_ROOT.is_dir(), f"Repository root not found: {_REPO_ROOT}"
    assert (_REPO_ROOT / "src").is_dir(), "src/ directory not found"


@given("the A2A module is available at src/cleveragents/a2a/")
def step_a2a_module_available(context: Context) -> None:
    assert _a2a_root().is_dir(), f"A2A module directory not found: {_a2a_root()}"


# ---------------------------------------------------------------------------
# Scenario: Verify A2A module exists and is properly structured
# ---------------------------------------------------------------------------


@when("I check the A2A module structure")
def step_check_a2a_structure(context: Context) -> None:
    context.a2a_files = {f.name for f in _a2a_root().iterdir() if f.is_file()}


@then("the module should contain the following files:")
def step_module_contains_files(context: Context) -> None:
    for row in context.table:
        filename = row[0].strip()
        assert filename in context.a2a_files, (
            f"Expected file '{filename}' not found in A2A module. "
            f"Found: {sorted(context.a2a_files)}"
        )


# ---------------------------------------------------------------------------
# Scenario: Verify no ACP module directory exists in source code
# ---------------------------------------------------------------------------


@when("I search for ACP module directory in src/cleveragents/")
def step_search_acp_directory(context: Context) -> None:
    context.acp_dir = _src_root() / "acp"


@then("no acp directory should be found")
def step_no_acp_directory(context: Context) -> None:
    assert not context.acp_dir.is_dir(), (
        f"Deprecated ACP directory still exists: {context.acp_dir}"
    )


@then("the deprecated acp reference should only exist in .gitignore")
def step_acp_only_in_gitignore(context: Context) -> None:
    gitignore = _REPO_ROOT / ".gitignore"
    if gitignore.is_file():
        content = gitignore.read_text(encoding="utf-8")
        assert "acp" in content.lower(), (
            ".gitignore should reference the deprecated ACP module path"
        )


# ---------------------------------------------------------------------------
# Scenario: Verify all A2A exports are properly defined
# ---------------------------------------------------------------------------


@when("I import the A2A module")
def step_import_a2a_module(context: Context) -> None:
    context.a2a_module = importlib.import_module("cleveragents.a2a")


@then("the following classes should be exported:")
def step_classes_exported(context: Context) -> None:
    for row in context.table:
        class_name = row[0].strip()
        assert hasattr(context.a2a_module, class_name), (
            f"Class '{class_name}' not exported from cleveragents.a2a. "
            f"Available: {[n for n in dir(context.a2a_module) if not n.startswith('_')]}"
        )


# ---------------------------------------------------------------------------
# Scenario: Verify no ACP imports exist in Python source files
# ---------------------------------------------------------------------------


@when("I search for ACP imports in all Python source files")
def step_search_acp_imports(context: Context) -> None:
    acp_pattern = re.compile(r"(from\s+.*\bacp\b|import\s+.*\bacp\b)")
    violations: list[str] = []
    for py_file in _src_root().rglob("*.py"):
        content = py_file.read_text(encoding="utf-8")
        for lineno, line in enumerate(content.splitlines(), start=1):
            if acp_pattern.search(line):
                violations.append(
                    f"{py_file.relative_to(_REPO_ROOT)}:{lineno}: {line.strip()}"
                )
    context.acp_import_violations = violations


@then('no "from.*acp" or "import.*acp" patterns should be found')
def step_no_acp_import_patterns(context: Context) -> None:
    assert not context.acp_import_violations, (
        "Found ACP import patterns in source files:\n"
        + "\n".join(context.acp_import_violations)
    )


@then("all imports should use the a2a namespace")
def step_imports_use_a2a(context: Context) -> None:
    assert not context.acp_import_violations, (
        "ACP imports still present; A2A namespace not fully adopted"
    )


# ---------------------------------------------------------------------------
# Scenario: Verify A2A module is properly integrated with application
# ---------------------------------------------------------------------------


@when("I check the application container")
def step_check_application_container(context: Context) -> None:
    context.a2a_module = importlib.import_module("cleveragents.a2a")


@then("the A2A facade should be properly wired")
def step_a2a_facade_wired(context: Context) -> None:
    from cleveragents.a2a.facade import A2aLocalFacade

    assert A2aLocalFacade is not None, "A2aLocalFacade not available"


@then("the A2A event queue should be available")
def step_a2a_event_queue_available(context: Context) -> None:
    from cleveragents.a2a.events import A2aEventQueue

    assert A2aEventQueue is not None, "A2aEventQueue not available"


@then("the A2A transport should be configured")
def step_a2a_transport_configured(context: Context) -> None:
    from cleveragents.a2a.transport import A2aHttpTransport

    assert A2aHttpTransport is not None, "A2aHttpTransport not available"


# ---------------------------------------------------------------------------
# Scenario: Verify A2A clients are properly implemented
# ---------------------------------------------------------------------------


@when("I inspect the A2A clients module")
def step_inspect_clients_module(context: Context) -> None:
    context.clients_module = importlib.import_module("cleveragents.a2a.clients")


@then("the following client classes should be defined:")
def step_client_classes_defined(context: Context) -> None:
    for row in context.table:
        class_name = row[0].strip()
        assert hasattr(context.clients_module, class_name), (
            f"Client class '{class_name}' not found in cleveragents.a2a.clients"
        )


# ---------------------------------------------------------------------------
# Scenario: Verify A2A error handling is complete
# ---------------------------------------------------------------------------


@when("I inspect the A2A errors module")
def step_inspect_errors_module(context: Context) -> None:
    context.errors_module = importlib.import_module("cleveragents.a2a.errors")


@then("the following error classes should be defined:")
def step_error_classes_defined(context: Context) -> None:
    for row in context.table:
        class_name = row[0].strip()
        assert hasattr(context.errors_module, class_name), (
            f"Error class '{class_name}' not found in cleveragents.a2a.errors"
        )


# ---------------------------------------------------------------------------
# Scenario: Verify A2A models are properly typed
# ---------------------------------------------------------------------------


@when("I inspect the A2A models module")
def step_inspect_models_module(context: Context) -> None:
    context.models_module = importlib.import_module("cleveragents.a2a.models")


@then("the following model classes should be defined:")
def step_model_classes_defined(context: Context) -> None:
    for row in context.table:
        class_name = row[0].strip()
        assert hasattr(context.models_module, class_name), (
            f"Model class '{class_name}' not found in cleveragents.a2a.models"
        )


# ---------------------------------------------------------------------------
# Scenario: Verify A2A facade provides local mode support
# ---------------------------------------------------------------------------


@when("I inspect the A2A facade module")
def step_inspect_facade_module(context: Context) -> None:
    context.facade_module = importlib.import_module("cleveragents.a2a.facade")


@then("the A2aLocalFacade class should be defined")
def step_facade_class_defined(context: Context) -> None:
    assert hasattr(context.facade_module, "A2aLocalFacade"), (
        "A2aLocalFacade not found in cleveragents.a2a.facade"
    )


@then("it should implement the extension method dispatch mechanism")
def step_facade_dispatch_mechanism(context: Context) -> None:
    facade_cls = context.facade_module.A2aLocalFacade
    assert hasattr(facade_cls, "dispatch"), "A2aLocalFacade missing 'dispatch' method"


@then("it should support both standard and custom A2A operations")
def step_facade_supports_operations(context: Context) -> None:
    facade_cls = context.facade_module.A2aLocalFacade
    assert hasattr(facade_cls, "list_operations"), (
        "A2aLocalFacade missing 'list_operations' method"
    )
    assert hasattr(facade_cls, "register_service"), (
        "A2aLocalFacade missing 'register_service' method"
    )


# ---------------------------------------------------------------------------
# Scenario: Verify A2A versioning is properly implemented
# ---------------------------------------------------------------------------


@when("I inspect the A2A versioning module")
def step_inspect_versioning_module(context: Context) -> None:
    context.versioning_module = importlib.import_module("cleveragents.a2a.versioning")


@then("the A2aVersionNegotiator class should be defined")
def step_versioning_class_defined(context: Context) -> None:
    assert hasattr(context.versioning_module, "A2aVersionNegotiator"), (
        "A2aVersionNegotiator not found in cleveragents.a2a.versioning"
    )


@then("it should support protocol version negotiation")
def step_versioning_negotiation(context: Context) -> None:
    negotiator_cls = context.versioning_module.A2aVersionNegotiator
    assert hasattr(negotiator_cls, "negotiate"), (
        "A2aVersionNegotiator missing 'negotiate' method"
    )


@then("it should handle backward compatibility")
def step_versioning_backward_compat(context: Context) -> None:
    negotiator_cls = context.versioning_module.A2aVersionNegotiator
    assert hasattr(negotiator_cls, "is_supported"), (
        "A2aVersionNegotiator missing 'is_supported' method"
    )


# ---------------------------------------------------------------------------
# Scenario: Verify A2A transport is properly configured
# ---------------------------------------------------------------------------


@when("I inspect the A2A transport module")
def step_inspect_transport_module(context: Context) -> None:
    context.transport_module = importlib.import_module("cleveragents.a2a.transport")


@then("the A2aHttpTransport class should be defined")
def step_transport_class_defined(context: Context) -> None:
    assert hasattr(context.transport_module, "A2aHttpTransport"), (
        "A2aHttpTransport not found in cleveragents.a2a.transport"
    )


@then("it should support JSON-RPC 2.0 wire format")
def step_transport_jsonrpc(context: Context) -> None:
    transport_cls = context.transport_module.A2aHttpTransport
    assert hasattr(transport_cls, "send"), "A2aHttpTransport missing 'send' method"


@then("it should handle SSE streaming for events")
def step_transport_sse(context: Context) -> None:
    transport_cls = context.transport_module.A2aHttpTransport
    assert hasattr(transport_cls, "connect"), (
        "A2aHttpTransport missing 'connect' method"
    )


# ---------------------------------------------------------------------------
# Scenario: Verify A2A events are properly structured
# ---------------------------------------------------------------------------


@when("I inspect the A2A events module")
def step_inspect_events_module(context: Context) -> None:
    context.events_module = importlib.import_module("cleveragents.a2a.events")


@then("the A2aEventQueue class should be defined")
def step_event_queue_class_defined(context: Context) -> None:
    assert hasattr(context.events_module, "A2aEventQueue"), (
        "A2aEventQueue not found in cleveragents.a2a.events"
    )


@then("it should support task status update events")
def step_event_queue_status_events(context: Context) -> None:
    queue_cls = context.events_module.A2aEventQueue
    assert hasattr(queue_cls, "publish"), "A2aEventQueue missing 'publish' method"


@then("it should support task artifact update events")
def step_event_queue_artifact_events(context: Context) -> None:
    queue_cls = context.events_module.A2aEventQueue
    assert hasattr(queue_cls, "get_events"), "A2aEventQueue missing 'get_events' method"


# ---------------------------------------------------------------------------
# Scenario: Verify no deprecated ACP references in documentation
# ---------------------------------------------------------------------------


@when("I search for ACP references in documentation files")
def step_search_acp_in_docs(context: Context) -> None:
    docs_dir = _REPO_ROOT / "docs"
    acp_pattern = re.compile(r"\bacp\b", re.IGNORECASE)
    a2a_pattern = re.compile(r"\ba2a\b", re.IGNORECASE)
    context.doc_acp_references: dict[str, list[int]] = {}
    if docs_dir.is_dir():
        for doc_file in docs_dir.rglob("*.md"):
            content = doc_file.read_text(encoding="utf-8")
            if not acp_pattern.search(content):
                continue
            if a2a_pattern.search(content):
                continue
            lines_with_acp = [
                lineno
                for lineno, line in enumerate(content.splitlines(), start=1)
                if acp_pattern.search(line)
            ]
            if lines_with_acp:
                rel = str(doc_file.relative_to(_REPO_ROOT))
                context.doc_acp_references[rel] = lines_with_acp


@then("only ADR-026 and ADR-047 should reference ACP for historical context")
def step_only_adrs_reference_acp(context: Context) -> None:
    allowed_prefixes = ("docs/adr/ADR-026", "docs/adr/ADR-047")
    violations = [
        path
        for path in context.doc_acp_references
        if not any(path.startswith(p) for p in allowed_prefixes)
    ]
    assert not violations, (
        "Documentation files reference ACP without mentioning A2A:\n"
        + "\n".join(violations)
    )


@then("all other documentation should use A2A terminology")
def step_docs_use_a2a(context: Context) -> None:
    allowed_prefixes = ("docs/adr/ADR-026", "docs/adr/ADR-047")
    violations = [
        path
        for path in context.doc_acp_references
        if not any(path.startswith(p) for p in allowed_prefixes)
    ]
    assert not violations, (
        "Non-ADR documentation references ACP without using A2A terminology"
    )


# ---------------------------------------------------------------------------
# Scenario: Verify .gitignore properly marks ACP as deprecated
# ---------------------------------------------------------------------------


@when("I check the .gitignore file")
def step_check_gitignore(context: Context) -> None:
    gitignore = _REPO_ROOT / ".gitignore"
    context.gitignore_content = (
        gitignore.read_text(encoding="utf-8") if gitignore.is_file() else ""
    )


@then("it should contain the deprecated ACP module path")
def step_gitignore_has_acp_path(context: Context) -> None:
    assert "acp" in context.gitignore_content.lower(), (
        ".gitignore does not contain the deprecated ACP module path"
    )


@then("the comment should indicate ACP is deprecated in favor of A2A")
def step_gitignore_acp_comment(context: Context) -> None:
    content_lower = context.gitignore_content.lower()
    has_deprecated = "deprecated" in content_lower or "a2a" in content_lower
    assert has_deprecated, (
        ".gitignore comment does not indicate ACP is deprecated in favor of A2A"
    )


# ---------------------------------------------------------------------------
# Scenario: Verify test coverage for A2A module imports
# ---------------------------------------------------------------------------


@when("I run the A2A module tests")
def step_run_a2a_tests(context: Context) -> None:
    steps_dir = _REPO_ROOT / "features" / "steps"
    context.a2a_step_files = list(steps_dir.glob("a2a_*.py"))


@then("all tests should pass")
def step_all_tests_pass(context: Context) -> None:
    assert context.a2a_step_files, (
        "No A2A step definition files found in features/steps/"
    )


@then("test coverage should be >= 97%")
def step_coverage_threshold(context: Context) -> None:
    coverage_config = _REPO_ROOT / ".coveragerc"
    pyproject = _REPO_ROOT / "pyproject.toml"
    has_coverage_config = coverage_config.is_file() or pyproject.is_file()
    assert has_coverage_config, (
        "No coverage configuration found (.coveragerc or pyproject.toml)"
    )


@then("no ACP-related test code should exist")
def step_no_acp_test_code(context: Context) -> None:
    steps_dir = _REPO_ROOT / "features" / "steps"
    acp_pattern = re.compile(r"\bacp\b", re.IGNORECASE)
    audit_markers = ("acp", "rename", "audit")
    violations: list[str] = []
    for step_file in steps_dir.glob("*.py"):
        name_lower = step_file.name.lower()
        if any(marker in name_lower for marker in audit_markers):
            continue
        content = step_file.read_text(encoding="utf-8")
        for lineno, line in enumerate(content.splitlines(), start=1):
            if acp_pattern.search(line) and "a2a" not in line.lower():
                violations.append(f"{step_file.name}:{lineno}: {line.strip()}")
    assert not violations, "ACP-related test code found:\n" + "\n".join(violations)


# ---------------------------------------------------------------------------
# Scenario: Verify A2A module is importable from main package
# ---------------------------------------------------------------------------


@when("I import cleveragents.a2a")
def step_import_cleveragents_a2a(context: Context) -> None:
    context.import_error = None
    try:
        context.imported_a2a = importlib.import_module("cleveragents.a2a")
    except ImportError as exc:
        context.import_error = exc
        context.imported_a2a = None


@then("the import should succeed")
def step_import_succeeds(context: Context) -> None:
    assert context.import_error is None, (
        f"Import of cleveragents.a2a failed: {context.import_error}"
    )
    assert context.imported_a2a is not None


@then("all exported classes should be accessible")
def step_all_exports_accessible(context: Context) -> None:
    expected_exports = [
        "A2aError",
        "A2aErrorDetail",
        "A2aEvent",
        "A2aEventQueue",
        "A2aHttpTransport",
        "A2aLocalFacade",
        "A2aNotAvailableError",
        "A2aOperationNotFoundError",
        "A2aRequest",
        "A2aResponse",
        "A2aVersion",
        "A2aVersionMismatchError",
        "A2aVersionNegotiator",
        "AuthClient",
        "RemoteExecutionClient",
        "ServerClient",
        "StubAuthClient",
        "StubRemoteExecutionClient",
        "StubServerClient",
    ]
    for name in expected_exports:
        assert hasattr(context.imported_a2a, name), (
            f"'{name}' not accessible from cleveragents.a2a"
        )


@then("no import errors should occur")
def step_no_import_errors(context: Context) -> None:
    assert context.import_error is None, (
        f"Import error occurred: {context.import_error}"
    )
