"""Step definitions for A2A architectural boundary enforcement tests."""

from __future__ import annotations

import ast
import json
from pathlib import Path
from typing import Any

import yaml
from behave import given, then, when


@given("the application services directory exists")
def step_application_services_dir_exists(context: Any) -> None:
    """Verify the application services directory exists."""
    context.app_services_dir = Path("src/cleveragents/application/services")
    assert context.app_services_dir.exists(), (
        f"Application services directory not found: {context.app_services_dir}"
    )


@when("I scan all application service modules for CLI imports")
def step_scan_application_services_for_cli_imports(context: Any) -> None:
    """Scan all application service modules for CLI imports."""
    context.cli_violations: list[str] = []
    for py_file in context.app_services_dir.rglob("*.py"):
        try:
            tree = ast.parse(py_file.read_text())
        except SyntaxError:
            continue
        for node in ast.walk(tree):
            if isinstance(node, ast.ImportFrom) and node.module:
                if node.module.startswith("cleveragents.cli"):
                    context.cli_violations.append(
                        f"{py_file}:{node.lineno}: from {node.module} import ..."
                    )
            elif isinstance(node, ast.Import):
                for alias in node.names:
                    if alias.name.startswith("cleveragents.cli"):
                        context.cli_violations.append(
                            f"{py_file}:{node.lineno}: import {alias.name}"
                        )


@then('no application service module should import from "cleveragents.cli"')
def step_no_cli_imports_in_application(context: Any) -> None:
    """Assert no application service imports from CLI layer."""
    violations = getattr(context, "cli_violations", [])
    assert not violations, (
        "Application services must not import from CLI (Presentation) layer.\n"
        "Violations found:\n" + "\n".join(violations)
    )


@given("the plan_apply_service module exists")
def step_plan_apply_service_exists(context: Any) -> None:
    """Verify plan_apply_service.py exists."""
    context.plan_apply_service_path = Path(
        "src/cleveragents/application/services/plan_apply_service.py"
    )
    assert context.plan_apply_service_path.exists(), "plan_apply_service.py not found"


@when("I inspect plan_apply_service imports")
def step_inspect_plan_apply_service_imports(context: Any) -> None:
    """Parse imports from plan_apply_service.py."""
    content = context.plan_apply_service_path.read_text()
    context.plan_apply_service_content = content
    try:
        tree = ast.parse(content)
    except SyntaxError as exc:
        raise AssertionError(f"Syntax error in plan_apply_service.py: {exc}") from exc
    context.plan_apply_imports: list[str] = []
    for node in ast.walk(tree):
        if isinstance(node, ast.ImportFrom) and node.module:
            context.plan_apply_imports.append(node.module)
        elif isinstance(node, ast.Import):
            for alias in node.names:
                context.plan_apply_imports.append(alias.name)


@then('it should not import from "cleveragents.cli.formatting"')
def step_plan_apply_service_no_cli_formatting(context: Any) -> None:
    """Assert plan_apply_service does not import from cli.formatting."""
    imports = getattr(context, "plan_apply_imports", [])
    cli_formatting_imports = [
        imp for imp in imports if imp == "cleveragents.cli.formatting"
    ]
    assert not cli_formatting_imports, (
        "plan_apply_service.py must not import from cleveragents.cli.formatting. "
        "Use cleveragents.shared.output_format instead."
    )


@then('it should import from "cleveragents.shared.output_format"')
def step_plan_apply_service_uses_shared_output(context: Any) -> None:
    """Assert plan_apply_service imports from shared.output_format."""
    content = getattr(context, "plan_apply_service_content", "")
    assert "cleveragents.shared.output_format" in content, (
        "plan_apply_service.py should import from "
        "cleveragents.shared.output_format for output formatting."
    )


@given("the shared output_format module exists")
def step_shared_output_format_exists(context: Any) -> None:
    """Verify shared/output_format.py exists."""
    context.output_format_path = Path("src/cleveragents/shared/output_format.py")
    assert context.output_format_path.exists(), "shared/output_format.py not found"


@when("I inspect shared output_format imports")
def step_inspect_shared_output_format_imports(context: Any) -> None:
    """Parse imports from shared/output_format.py."""
    content = context.output_format_path.read_text()
    try:
        tree = ast.parse(content)
    except SyntaxError as exc:
        raise AssertionError(f"Syntax error in shared/output_format.py: {exc}") from exc
    context.output_format_imports: list[str] = []
    for node in ast.walk(tree):
        if isinstance(node, ast.ImportFrom) and node.module:
            context.output_format_imports.append(node.module)
        elif isinstance(node, ast.Import):
            for alias in node.names:
                context.output_format_imports.append(alias.name)


@then('it should not import from "cleveragents.cli"')
def step_shared_output_format_no_cli_imports(context: Any) -> None:
    """Assert shared/output_format.py has no CLI imports."""
    imports = getattr(context, "output_format_imports", [])
    cli_imports = [imp for imp in imports if imp.startswith("cleveragents.cli")]
    assert not cli_imports, (
        "shared/output_format.py must not import from cleveragents.cli. "
        f"Found: {cli_imports}"
    )


@given("the shared output_format module is imported")
def step_import_shared_output_format(context: Any) -> None:
    """Import the shared output_format module."""
    from cleveragents.shared.output_format import format_data

    context.format_data = format_data


@when('I call format_data with a dict and format "json"')
def step_call_format_data_json(context: Any) -> None:
    """Call format_data with JSON format."""
    context.sample_data: dict[str, Any] = {
        "plan_id": "01HXYZ",
        "status": "applied",
        "count": 42,
    }
    context.format_result = context.format_data(context.sample_data, "json")


@when('I call format_data with a dict and format "yaml"')
def step_call_format_data_yaml(context: Any) -> None:
    """Call format_data with YAML format."""
    context.sample_data = {"plan_id": "01HXYZ", "status": "applied", "count": 42}
    context.format_result = context.format_data(context.sample_data, "yaml")


@when('I call format_data with a dict and format "plain"')
def step_call_format_data_plain(context: Any) -> None:
    """Call format_data with plain format."""
    context.sample_data = {"plan_id": "01HXYZ", "status": "applied", "count": 42}
    context.format_result = context.format_data(context.sample_data, "plain")


@when('I call format_data with a list of dicts and format "table"')
def step_call_format_data_table(context: Any) -> None:
    """Call format_data with table format."""
    context.sample_data = [
        {"plan_id": "01HXYZ", "status": "applied"},
        {"plan_id": "01HABC", "status": "pending"},
    ]
    context.format_result = context.format_data(context.sample_data, "table")


@when('I call format_data with a dict and format "unknown"')
def step_call_format_data_unknown(context: Any) -> None:
    """Call format_data with unknown format (should fall back to JSON)."""
    context.sample_data = {"plan_id": "01HXYZ", "status": "applied"}
    context.format_result = context.format_data(context.sample_data, "unknown")


@when('I call format_data with an empty list and format "table"')
def step_call_format_data_empty_table(context: Any) -> None:
    """Call format_data with empty list and table format."""
    context.format_result = context.format_data([], "table")


@when('I call format_data with a nested dict and format "plain"')
def step_call_format_data_nested_plain(context: Any) -> None:
    """Call format_data with nested dict and plain format."""
    context.sample_data = {
        "plan_id": "01HXYZ",
        "metadata": {"author": "test", "version": "1.0"},
    }
    context.format_result = context.format_data(context.sample_data, "plain")


@then("the result should be valid JSON")
def step_result_is_valid_json(context: Any) -> None:
    """Assert the result is valid JSON."""
    result = context.format_result
    assert isinstance(result, str), f"Expected str, got {type(result)}"
    try:
        json.loads(result)
    except json.JSONDecodeError as exc:
        raise AssertionError(
            f"Result is not valid JSON: {exc}\nResult: {result!r}"
        ) from exc


@then("the result should contain the expected keys")
def step_result_contains_expected_keys(context: Any) -> None:
    """Assert the JSON result contains expected keys."""
    result = json.loads(context.format_result)
    assert "plan_id" in result, f"Missing 'plan_id' in result: {result}"
    assert "status" in result, f"Missing 'status' in result: {result}"


@then("the result should be valid YAML")
def step_result_is_valid_yaml(context: Any) -> None:
    """Assert the result is valid YAML."""
    result = context.format_result
    assert isinstance(result, str), f"Expected str, got {type(result)}"
    try:
        parsed = yaml.safe_load(result)
        assert parsed is not None, "YAML parsed to None"
    except yaml.YAMLError as exc:
        raise AssertionError(
            f"Result is not valid YAML: {exc}\nResult: {result!r}"
        ) from exc


@then("the result should contain key-value pairs")
def step_result_contains_key_value_pairs(context: Any) -> None:
    """Assert the plain result contains key-value pairs."""
    result = context.format_result
    assert "plan_id" in result, f"Missing 'plan_id' in plain output: {result!r}"
    assert "status" in result, f"Missing 'status' in plain output: {result!r}"


@then("the result should contain column headers")
def step_result_contains_column_headers(context: Any) -> None:
    """Assert the table result contains column headers."""
    result = context.format_result
    assert "plan_id" in result, f"Missing 'plan_id' column in table: {result!r}"
    assert "status" in result, f"Missing 'status' column in table: {result!r}"


@then('the result should be "(empty)"')
def step_result_is_empty(context: Any) -> None:
    """Assert the result is the empty placeholder."""
    assert context.format_result == "(empty)", (
        f"Expected '(empty)', got: {context.format_result!r}"
    )


@then("the result should contain nested key-value pairs")
def step_result_contains_nested_pairs(context: Any) -> None:
    """Assert the plain result contains nested key-value pairs."""
    result = context.format_result
    assert "plan_id" in result, f"Missing 'plan_id' in plain output: {result!r}"
    assert "metadata" in result, f"Missing 'metadata' in plain output: {result!r}"


@when('I call format_data with a list of dicts and format "plain"')
def step_call_format_data_list_plain(context: Any) -> None:
    """Call format_data with list of dicts and plain format."""
    context.sample_data = [
        {"plan_id": "01HXYZ", "status": "applied"},
        {"plan_id": "01HABC", "status": "pending"},
    ]
    context.format_result = context.format_data(context.sample_data, "plain")


@then("the result should contain multiple entries separated by dashes")
def step_result_contains_multiple_entries(context: Any) -> None:
    """Assert the plain result contains multiple entries separated by ---."""
    result = context.format_result
    assert "plan_id" in result, f"Missing 'plan_id' in plain output: {result!r}"
    assert "---" in result, f"Missing separator '---' in plain output: {result!r}"


@when('I call format_data with a dict containing list values and format "plain"')
def step_call_format_data_dict_with_list_plain(context: Any) -> None:
    """Call format_data with dict containing list values and plain format."""
    context.sample_data = {
        "plan_id": "01HXYZ",
        "tags": ["tag1", "tag2", "tag3"],
    }
    context.format_result = context.format_data(context.sample_data, "plain")


@then("the result should contain list items with dash prefix")
def step_result_contains_list_items(context: Any) -> None:
    """Assert the plain result contains list items with dash prefix."""
    result = context.format_result
    assert "plan_id" in result, f"Missing 'plan_id' in plain output: {result!r}"
    assert "tags" in result, f"Missing 'tags' in plain output: {result!r}"
    assert "- tag1" in result, f"Missing '- tag1' in plain output: {result!r}"


@when('I call format_data with a dict containing a datetime value and format "json"')
def step_call_format_data_datetime_json(context: Any) -> None:
    """Call format_data with dict containing datetime value and JSON format."""
    from datetime import UTC, datetime

    context.sample_datetime = datetime(2026, 4, 24, 12, 0, 0, tzinfo=UTC)
    context.sample_data = {
        "plan_id": "01HXYZ",
        "created_at": context.sample_datetime,
    }
    context.format_result = context.format_data(context.sample_data, "json")


@then("the datetime value should be serialized as ISO string")
def step_datetime_serialized_as_iso(context: Any) -> None:
    """Assert the datetime value is serialized as ISO string."""
    result = json.loads(context.format_result)
    assert "created_at" in result, f"Missing 'created_at' in result: {result}"
    assert isinstance(result["created_at"], str), (
        f"Expected str for datetime, got {type(result['created_at'])}"
    )
    assert "2026-04-24" in result["created_at"], (
        f"Expected ISO date in 'created_at': {result['created_at']}"
    )


@when('I call format_data with a dict containing an enum value and format "json"')
def step_call_format_data_enum_json(context: Any) -> None:
    """Call format_data with dict containing enum value and JSON format."""
    from enum import Enum

    class StatusEnum(Enum):
        APPLIED = "applied"
        PENDING = "pending"

    context.sample_data = {
        "plan_id": "01HXYZ",
        "status": StatusEnum.APPLIED,
    }
    context.format_result = context.format_data(context.sample_data, "json")


@then("the enum value should be serialized as its value")
def step_enum_serialized_as_value(context: Any) -> None:
    """Assert the enum value is serialized as its .value."""
    result = json.loads(context.format_result)
    assert "status" in result, f"Missing 'status' in result: {result}"
    assert result["status"] == "applied", (
        f"Expected 'applied' for enum value, got {result['status']!r}"
    )


@when('I call format_data with a single dict and format "table"')
def step_call_format_data_single_dict_table(context: Any) -> None:
    """Call format_data with single dict and table format."""
    context.sample_data = {"plan_id": "01HXYZ", "status": "applied"}
    context.format_result = context.format_data(context.sample_data, "table")


@when('I call format_data with a list containing dict values and format "table"')
def step_call_format_data_list_with_dict_values_table(context: Any) -> None:
    """Call format_data with list containing dict values and table format."""
    context.sample_data = [
        {"plan_id": "01HXYZ", "metadata": {"key": "value"}},
        {"plan_id": "01HABC", "metadata": {"key": "other"}},
    ]
    context.format_result = context.format_data(context.sample_data, "table")


@then("the result should contain serialized dict values")
def step_result_contains_serialized_dict_values(context: Any) -> None:
    """Assert the table result contains serialized dict values."""
    result = context.format_result
    assert "plan_id" in result, f"Missing 'plan_id' in table output: {result!r}"
    assert "metadata" in result, f"Missing 'metadata' in table output: {result!r}"
