"""Step definitions for provider_registry_coverage_boost.feature.

These steps target specific uncovered lines in
src/cleveragents/providers/registry.py identified from build/coverage.xml.
"""

from __future__ import annotations

import os
import sys
import types
from typing import Any
from unittest.mock import MagicMock

from behave import given, then, when  # type: ignore[import-untyped]

from cleveragents.providers.registry import (
    ProviderRegistry,
    ProviderType,
)

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


def _make_boost_settings(
    openai: str | None = None,
    anthropic: str | None = None,
    google: str | None = None,
    gemini: str | None = None,
    azure: str | None = None,
    openrouter: str | None = None,
    cohere: str | None = None,
    groq: str | None = None,
    together: str | None = None,
    default_provider: str | None = None,
    default_model: str | None = None,
    azure_endpoint: str | None = None,
    azure_api_version: str | None = None,
    azure_deployment: str | None = None,
    openrouter_org: str | None = None,
) -> MagicMock:
    """Return a fake Settings-like object populated with API keys."""
    settings = MagicMock()
    settings.openai_api_key = openai
    settings.anthropic_api_key = anthropic
    settings.google_api_key = google
    settings.gemini_api_key = gemini
    settings.azure_api_key = azure
    settings.openrouter_api_key = openrouter
    settings.cohere_api_key = cohere
    settings.groq_api_key = groq
    settings.together_api_key = together
    settings.default_provider = default_provider
    settings.default_model = default_model
    if azure and not azure_endpoint:
        azure_endpoint = "https://example.openai.azure.com"
    settings.azure_openai_endpoint = azure_endpoint
    settings.azure_openai_api_version = azure_api_version
    settings.azure_openai_deployment = azure_deployment
    settings.openrouter_organization = openrouter_org
    return settings


def _stub_azure_langchain(context: Any) -> None:
    """Stub the langchain_openai module so AzureChatOpenAI is a recorder."""
    original = sys.modules.get("langchain_openai")
    calls: list[dict[str, Any]] = []

    class FakeAzureChatOpenAI:
        def __init__(self, **kwargs: Any) -> None:
            calls.append(kwargs)

    # Also provide ChatOpenAI so other imports from the module don't break.
    class FakeChatOpenAI:
        def __init__(self, **kwargs: Any) -> None:
            pass

    stub = types.ModuleType("langchain_openai")
    stub.AzureChatOpenAI = FakeAzureChatOpenAI
    stub.ChatOpenAI = FakeChatOpenAI
    sys.modules["langchain_openai"] = stub

    context._boost_azure_calls = calls
    context._boost_azure_cls = FakeAzureChatOpenAI

    if not hasattr(context, "_boost_cleanup"):
        context._boost_cleanup = []

    def _restore() -> None:
        if original is None:
            sys.modules.pop("langchain_openai", None)
        else:
            sys.modules["langchain_openai"] = original

    context._boost_cleanup.append(_restore)


# ---------------------------------------------------------------------------
# Given
# ---------------------------------------------------------------------------


@given(
    'a registry where settings default_provider is "{default_prov}" but only anthropic key is set'
)
def step_boost_registry_settings_default_unconfigured(
    context: Any, default_prov: str
) -> None:
    """Create a registry whose settings.default_provider points to a valid but
    unconfigured provider, while another provider IS configured."""
    context.boost_registry = ProviderRegistry(
        settings=_make_boost_settings(
            anthropic="sk-ant-test",
            default_provider=default_prov,
        )
    )


@given("CLEVERAGENTS_DEFAULT_PROVIDER env var is cleared")
def step_boost_clear_default_provider_env(context: Any) -> None:
    context._boost_orig_provider_env = os.environ.get("CLEVERAGENTS_DEFAULT_PROVIDER")
    os.environ.pop("CLEVERAGENTS_DEFAULT_PROVIDER", None)


@given('a registry with azure API key and deployment setting "{deployment}"')
def step_boost_azure_registry_with_deployment(context: Any, deployment: str) -> None:
    context.boost_registry = ProviderRegistry(
        settings=_make_boost_settings(
            azure="sk-azure-test",
            azure_deployment=deployment,
        )
    )


@given("a registry with azure API key and no deployment setting")
def step_boost_azure_registry_no_deployment(context: Any) -> None:
    context.boost_registry = ProviderRegistry(
        settings=_make_boost_settings(
            azure="sk-azure-test",
            azure_deployment=None,
        )
    )


@given("the Azure LangChain client is stubbed for boost")
def step_boost_stub_azure(context: Any) -> None:
    _stub_azure_langchain(context)


@given("a registry with google API key configured for boost")
def step_boost_google_registry(context: Any) -> None:
    context.boost_registry = ProviderRegistry(
        settings=_make_boost_settings(google="sk-google-test")
    )


@given("a registry with no API keys for boost")
def step_boost_no_keys_registry(context: Any) -> None:
    context.boost_registry = ProviderRegistry(settings=_make_boost_settings())


@given("a registry with openrouter API key configured for boost")
def step_boost_openrouter_registry(context: Any) -> None:
    context.boost_registry = ProviderRegistry(
        settings=_make_boost_settings(openrouter="sk-or-test")
    )


# ---------------------------------------------------------------------------
# When
# ---------------------------------------------------------------------------


@when("I request the default provider type from the boost registry")
def step_boost_get_default_provider_type(context: Any) -> None:
    context.boost_result = context.boost_registry.get_default_provider_type()


@when("I call _create_provider_llm for AZURE with model_id None via boost")
def step_boost_create_azure_llm_no_model(context: Any) -> None:
    context.boost_result = context.boost_registry._create_provider_llm(
        ProviderType.AZURE, None
    )


@when("I create an AI provider for google with empty string model_id via boost")
def step_boost_google_ai_provider_empty_model(context: Any) -> None:
    # Ensure env vars don't interfere
    orig_model = os.environ.pop("CLEVERAGENTS_DEFAULT_MODEL", None)
    context._boost_orig_model_env = orig_model
    context.boost_registry._settings.default_model = None
    context.boost_ai_provider = context.boost_registry.create_ai_provider(
        provider_type="google", model_id=""
    )


@when("I try to create an AI provider for openrouter via boost")
def step_boost_try_openrouter_ai_provider(context: Any) -> None:
    try:
        context.boost_registry.create_ai_provider(provider_type="openrouter")
        context.boost_error = None
    except ValueError as exc:
        context.boost_error = exc


@when("I create an AI provider for openrouter with empty string model_id via boost")
def step_boost_openrouter_ai_provider_empty_model(context: Any) -> None:
    orig_model = os.environ.pop("CLEVERAGENTS_DEFAULT_MODEL", None)
    context._boost_orig_model_env = orig_model
    context.boost_registry._settings.default_model = None
    context.boost_ai_provider = context.boost_registry.create_ai_provider(
        provider_type="openrouter", model_id=""
    )


# ---------------------------------------------------------------------------
# Then
# ---------------------------------------------------------------------------


@then("the boost result should be ProviderType ANTHROPIC")
def step_boost_result_is_anthropic(context: Any) -> None:
    assert context.boost_result == ProviderType.ANTHROPIC, (
        f"Expected ProviderType.ANTHROPIC, got {context.boost_result!r}"
    )


@then('the stubbed Azure client deployment_name should be "{expected}"')
def step_boost_azure_deployment_name(context: Any, expected: str) -> None:
    calls = context._boost_azure_calls
    assert calls, "Expected AzureChatOpenAI to be instantiated"
    actual = calls[-1].get("deployment_name")
    assert actual == expected, f"Expected deployment_name={expected!r}, got {actual!r}"


@then('the boost AI provider model_id should be "{expected}"')
def step_boost_ai_provider_model(context: Any, expected: str) -> None:
    assert context.boost_ai_provider.model_id == expected, (
        f"Expected model_id={expected!r}, got {context.boost_ai_provider.model_id!r}"
    )


@then("a boost ValueError should be raised")
def step_boost_value_error(context: Any) -> None:
    assert context.boost_error is not None, "Expected a ValueError to be raised"
    assert isinstance(context.boost_error, ValueError), (
        f"Expected ValueError, got {type(context.boost_error).__name__}"
    )


@then('the boost error message should contain "{fragment}"')
def step_boost_error_contains(context: Any, fragment: str) -> None:
    message = str(context.boost_error).lower()
    assert fragment.lower() in message, (
        f"Expected error to contain {fragment!r}, got: {message!r}"
    )
