"""Step definitions for ``agent_card.feature``.

Tests Agent Card model construction, conformance validation,
serialization, discovery endpoint, and skill enumeration.
"""

from __future__ import annotations

from behave import given, then, when  # type: ignore[import-untyped]
from behave.runner import Context
from fastapi.testclient import TestClient
from pydantic import ValidationError

from cleveragents.a2a.agent_card import (
    AgentCard,
    AgentCardExtension,
    AgentCardInterface,
    AgentCardSecurityScheme,
    AgentCardSkill,
    agent_card_to_dict,
    build_agent_card,
    validate_agent_card_conformance,
)
from cleveragents.a2a.facade import A2aLocalFacade
from cleveragents.infrastructure.server.asgi_app import create_asgi_app

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


@given("an A2aLocalFacade for agent card testing")
def step_given_facade_for_card(context: Context) -> None:
    context.facade = A2aLocalFacade()


@given("an agent card with empty url")
def step_given_card_empty_url(context: Context) -> None:
    context.agent_card = AgentCard(
        name="Test",
        version="1.0",
        url="",
    )


@given('an agent card with version "{version}"')
def step_given_card_version(context: Context, version: str) -> None:
    context.agent_card = AgentCard(
        name="Test",
        version=version,
        url="http://localhost/a2a",
    )


@given("an agent card with empty supported versions")
def step_given_card_empty_supported_versions(context: Context) -> None:
    context.agent_card = AgentCard(
        name="Test",
        version="1.0",
        url="http://localhost/a2a",
        supportedVersions=[],
    )


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


@when("I build an agent card from the facade")
def step_build_card_from_facade(context: Context) -> None:
    context.agent_card = build_agent_card(facade=context.facade)


@when("I build an agent card without a facade")
def step_build_card_no_facade(context: Context) -> None:
    context.agent_card = build_agent_card()


@when("I try to build an agent card with an empty host")
def step_try_build_card_empty_host(context: Context) -> None:
    context.caught_exception = None
    try:
        build_agent_card(host="")
    except ValueError as exc:
        context.caught_exception = exc


@when("I try to build an agent card with port {port:d}")
def step_try_build_card_bad_port(context: Context, port: int) -> None:
    context.caught_exception = None
    try:
        build_agent_card(port=port)
    except ValueError as exc:
        context.caught_exception = exc


# ---------------------------------------------------------------------------
# When steps -- model validation
# ---------------------------------------------------------------------------


@when("I try to create an AgentCard with empty name")
def step_try_card_empty_name(context: Context) -> None:
    context.caught_exception = None
    try:
        AgentCard(name="", version="1.0")
    except ValidationError as exc:
        context.caught_exception = exc


@when("I try to create an AgentCard with empty version")
def step_try_card_empty_version(context: Context) -> None:
    context.caught_exception = None
    try:
        AgentCard(name="Test", version="")
    except ValidationError as exc:
        context.caught_exception = exc


@when("I try to create an AgentCardSkill with empty id")
def step_try_skill_empty_id(context: Context) -> None:
    context.caught_exception = None
    try:
        AgentCardSkill(id="", name="Test")
    except ValidationError as exc:
        context.caught_exception = exc


@when("I try to create an AgentCardSkill with empty skill name")
def step_try_skill_empty_name(context: Context) -> None:
    context.caught_exception = None
    try:
        AgentCardSkill(id="test", name="")
    except ValidationError as exc:
        context.caught_exception = exc


@when("I try to create an AgentCardExtension with empty uri")
def step_try_extension_empty_uri(context: Context) -> None:
    context.caught_exception = None
    try:
        AgentCardExtension(uri="")
    except ValidationError as exc:
        context.caught_exception = exc


@when("I try to create an AgentCardSecurityScheme with empty type")
def step_try_security_empty_type(context: Context) -> None:
    context.caught_exception = None
    try:
        AgentCardSecurityScheme(type="")
    except ValidationError as exc:
        context.caught_exception = exc


@when("I try to create an AgentCardInterface with empty protocol")
def step_try_interface_empty_protocol(context: Context) -> None:
    context.caught_exception = None
    try:
        AgentCardInterface(protocol="")
    except ValidationError as exc:
        context.caught_exception = exc


# ---------------------------------------------------------------------------
# When steps -- conformance validation
# ---------------------------------------------------------------------------


@when("I validate the agent card conformance")
def step_validate_conformance(context: Context) -> None:
    context.violations = validate_agent_card_conformance(context.agent_card)


@when("I try to validate conformance of a non-AgentCard object")
def step_try_validate_non_card(context: Context) -> None:
    context.caught_exception = None
    try:
        validate_agent_card_conformance("not-a-card")  # type: ignore[arg-type]
    except TypeError as exc:
        context.caught_exception = exc


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


@when("I serialize the agent card to dict")
def step_serialize_card(context: Context) -> None:
    context.card_dict = agent_card_to_dict(context.agent_card)


@when("I try to serialize a non-AgentCard object")
def step_try_serialize_non_card(context: Context) -> None:
    context.caught_exception = None
    try:
        agent_card_to_dict("not-a-card")  # type: ignore[arg-type]
    except TypeError as exc:
        context.caught_exception = exc


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


@given("a running ASGI test client with facade")
def step_given_test_client_with_facade(context: Context) -> None:
    facade = A2aLocalFacade()
    app = create_asgi_app(facade=facade)
    context.test_client = TestClient(app)


@when("I request GET /.well-known/agent.json and parse JSON")
def step_get_agent_card_parse(context: Context) -> None:
    context.response = context.test_client.get("/.well-known/agent.json")
    context.card_json = context.response.json()


# ---------------------------------------------------------------------------
# Then steps -- card properties
# ---------------------------------------------------------------------------


@then('the agent card name should be "{expected}"')
def step_check_card_name(context: Context, expected: str) -> None:
    assert context.agent_card.name == expected, (
        f"Expected name '{expected}', got '{context.agent_card.name}'"
    )


@then('the agent card version should be "{expected}"')
def step_check_card_version(context: Context, expected: str) -> None:
    assert context.agent_card.version == expected, (
        f"Expected version '{expected}', got '{context.agent_card.version}'"
    )


@then("the agent card should have a non-empty url")
def step_check_card_url(context: Context) -> None:
    assert context.agent_card.url, "Expected non-empty url"


@then("the agent card should have at least {count:d} skill")
@then("the agent card should have at least {count:d} skills")
def step_check_card_skills_count(context: Context, count: int) -> None:
    actual = len(context.agent_card.skills)
    assert actual >= count, f"Expected at least {count} skills, got {actual}"


@then("the agent card should have {count:d} skills")
def step_check_card_exact_skills(context: Context, count: int) -> None:
    actual = len(context.agent_card.skills)
    assert actual == count, f"Expected {count} skills, got {actual}"


@then("the agent card should have at least {count:d} extension")
@then("the agent card should have at least {count:d} extensions")
def step_check_card_extensions_count(context: Context, count: int) -> None:
    actual = len(context.agent_card.extensions)
    assert actual >= count, f"Expected at least {count} extensions, got {actual}"


@then("the agent card should have at least {count:d} security scheme")
@then("the agent card should have at least {count:d} security schemes")
def step_check_card_security_count(context: Context, count: int) -> None:
    actual = len(context.agent_card.securitySchemes)
    assert actual >= count, f"Expected at least {count} security schemes, got {actual}"


@then("the agent card should have at least {count:d} interface")
@then("the agent card should have at least {count:d} interfaces")
def step_check_card_interfaces_count(context: Context, count: int) -> None:
    actual = len(context.agent_card.interfaces)
    assert actual >= count, f"Expected at least {count} interfaces, got {actual}"


@then('the first interface protocol should be "{expected}"')
def step_check_first_interface(context: Context, expected: str) -> None:
    assert context.agent_card.interfaces, "No interfaces found"
    assert context.agent_card.interfaces[0].protocol == expected


@then('the agent card supported versions should contain "{version}"')
def step_check_supported_versions(context: Context, version: str) -> None:
    assert version in context.agent_card.supportedVersions, (
        f"'{version}' not in {context.agent_card.supportedVersions}"
    )


@then("the agent card should list extension operations")
def step_check_extension_ops(context: Context) -> None:
    assert context.agent_card.supportedOperations, (
        "Expected non-empty supportedOperations"
    )
    for op in context.agent_card.supportedOperations:
        assert op.startswith("_cleveragents/"), f"Operation '{op}' has wrong prefix"


@then('the agent card default input modes should contain "{mode}"')
def step_check_input_modes(context: Context, mode: str) -> None:
    assert mode in context.agent_card.defaultInputModes


@then('the agent card default output modes should contain "{mode}"')
def step_check_output_modes(context: Context, mode: str) -> None:
    assert mode in context.agent_card.defaultOutputModes


@then("the agent card streaming capability should be false")
def step_check_streaming_false(context: Context) -> None:
    assert context.agent_card.capabilities.streaming is False


@then("the agent card push notifications capability should be false")
def step_check_push_false(context: Context) -> None:
    assert context.agent_card.capabilities.pushNotifications is False


# ---------------------------------------------------------------------------
# Then steps -- validation errors
# ---------------------------------------------------------------------------


@then("a ValueError should be raised for agent card")
def step_check_value_error_card(context: Context) -> None:
    assert context.caught_exception is not None, "Expected ValueError"
    assert isinstance(context.caught_exception, ValueError)


@then("a validation error should be raised for agent card")
def step_check_validation_error_card(context: Context) -> None:
    assert context.caught_exception is not None, "Expected ValidationError"
    assert isinstance(context.caught_exception, ValidationError)


@then("a TypeError should be raised for conformance validation")
def step_check_type_error_conformance(context: Context) -> None:
    assert context.caught_exception is not None, "Expected TypeError"
    assert isinstance(context.caught_exception, TypeError)


@then("a TypeError should be raised for serialization")
def step_check_type_error_serialization(context: Context) -> None:
    assert context.caught_exception is not None, "Expected TypeError"
    assert isinstance(context.caught_exception, TypeError)


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


@then("the conformance violations should be empty")
def step_check_no_violations(context: Context) -> None:
    assert context.violations == [], (
        f"Expected no violations, got: {context.violations}"
    )


@then('the conformance violations should include "{text}"')
def step_check_violation_text(context: Context, text: str) -> None:
    joined = " | ".join(context.violations)
    assert any(text in v for v in context.violations), (
        f"Expected violation containing '{text}', got: {joined}"
    )


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


@then('the serialized dict should have key "{key}"')
def step_check_dict_key(context: Context, key: str) -> None:
    assert key in context.card_dict, (
        f"Key '{key}' not in serialized dict: {list(context.card_dict.keys())}"
    )


# ---------------------------------------------------------------------------
# Then steps -- JSON endpoint
# ---------------------------------------------------------------------------


@then('the agent card JSON should have key "{key}" with value "{value}"')
def step_check_json_key_value(context: Context, key: str, value: str) -> None:
    actual = context.card_json.get(key)
    assert actual == value, f"Expected '{key}'='{value}', got '{actual}'"


@then('the agent card JSON "{key}" should be a dict')
def step_check_json_dict(context: Context, key: str) -> None:
    val = context.card_json.get(key)
    assert isinstance(val, dict), f"Expected dict for '{key}', got {type(val)}"


@then('the agent card JSON "{key}" should be a non-empty list')
def step_check_json_list(context: Context, key: str) -> None:
    val = context.card_json.get(key)
    assert isinstance(val, list), f"Expected list for '{key}', got {type(val)}"
    assert len(val) > 0, f"Expected non-empty list for '{key}'"


@then('all supported operations should start with "_cleveragents/"')
def step_check_ops_prefix(context: Context) -> None:
    ops = context.card_json.get("supportedOperations", [])
    for op in ops:
        assert op.startswith("_cleveragents/"), f"Operation '{op}' has wrong prefix"


# ---------------------------------------------------------------------------
# Then steps -- skill enumeration
# ---------------------------------------------------------------------------


@then('the agent card skills should include id "{skill_id}"')
def step_check_skill_id(context: Context, skill_id: str) -> None:
    ids = [s.id for s in context.agent_card.skills]
    assert skill_id in ids, f"Skill '{skill_id}' not in {ids}"


@given("an agent card with empty default input modes")
def step_given_card_empty_input_modes(context: Context) -> None:
    context.agent_card = AgentCard(
        name="Test",
        version="1.0",
        url="http://localhost/a2a",
        defaultInputModes=[],
    )


@given("an agent card with empty default output modes")
def step_given_card_empty_output_modes(context: Context) -> None:
    context.agent_card = AgentCard(
        name="Test",
        version="1.0",
        url="http://localhost/a2a",
        defaultOutputModes=[],
    )
