"""Step definitions for settings_coverage_boost.feature.

These steps target specific uncovered lines in settings.py:
- Lines 561-563: _max_delay_ge_base_delay model validator raising ValueError
- Lines 602, 604-608, 610-611: __repr__ method with sensitive-key masking
"""

from __future__ import annotations

import os

from behave import given, then, when
from pydantic import ValidationError

from cleveragents.config.settings import Settings
from cleveragents.shared.redaction import REDACTED

# Provider env var names that might leak into Settings from the test runner.
_PROVIDER_ENV_KEYS = (
    "OPENAI_API_KEY",
    "ANTHROPIC_API_KEY",
    "GOOGLE_API_KEY",
    "GOOGLE_GENAI_API_KEY",
    "AZURE_OPENAI_API_KEY",
    "AZURE_API_KEY",
    "OPENROUTER_API_KEY",
    "GEMINI_API_KEY",
    "GOOGLE_GEMINI_API_KEY",
    "HF_TOKEN",
    "HUGGINGFACEHUB_API_TOKEN",
    "HUGGING_FACE_HUB_TOKEN",
    "COHERE_API_KEY",
    "PERPLEXITY_API_KEY",
    "GROQ_API_KEY",
    "TOGETHER_API_KEY",
)


def _save_and_clear_provider_env(context):
    """Save current provider env vars and remove them so Settings sees None."""
    saved = {}
    for key in _PROVIDER_ENV_KEYS:
        val = os.environ.pop(key, None)
        if val is not None:
            saved[key] = val
    context._saved_provider_env = saved


def _restore_provider_env(context):
    """Restore previously saved provider env vars."""
    saved = getattr(context, "_saved_provider_env", {})
    for key, val in saved.items():
        os.environ[key] = val


# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("the settings coverage module is imported")
def step_settings_module_imported(context):
    """Ensure the Settings class is importable."""
    assert Settings is not None


# ---------------------------------------------------------------------------
# Scenario: _max_delay_ge_base_delay rejects invalid delay configuration
# (lines 561-563)
# ---------------------------------------------------------------------------
@when("I create Settings with retry_max_delay less than retry_base_delay")
def step_create_settings_bad_delays(context):
    """Attempt to create Settings where retry_max_delay < retry_base_delay."""
    context.delay_error = None
    # Use env vars because pydantic-settings reads these for Field values
    os.environ["CLEVERAGENTS_RETRY_BASE_DELAY"] = "10.0"
    os.environ["CLEVERAGENTS_RETRY_MAX_DELAY"] = "1.0"
    os.environ["CLEVERAGENTS_ENV"] = "test"
    os.environ["CLEVERAGENTS_MOCK_PROVIDERS"] = "true"
    try:
        Settings()
    except ValidationError as exc:
        context.delay_error = exc
    finally:
        os.environ.pop("CLEVERAGENTS_RETRY_BASE_DELAY", None)
        os.environ.pop("CLEVERAGENTS_RETRY_MAX_DELAY", None)
        os.environ.pop("CLEVERAGENTS_ENV", None)
        os.environ.pop("CLEVERAGENTS_MOCK_PROVIDERS", None)


@then("a ValidationError should be raised about delay ordering")
def step_check_delay_validation_error(context):
    """Verify that a ValidationError was raised."""
    assert context.delay_error is not None, (
        "Expected a ValidationError but none was raised"
    )
    assert isinstance(context.delay_error, ValidationError)


@then("the error message should mention both delay values")
def step_check_delay_error_message(context):
    """Verify the error message contains information about both delays."""
    msg = str(context.delay_error)
    assert "retry_max_delay" in msg, f"Expected 'retry_max_delay' in: {msg}"
    assert "retry_base_delay" in msg, f"Expected 'retry_base_delay' in: {msg}"


# ---------------------------------------------------------------------------
# Scenario: __repr__ masks sensitive field values when set
# (lines 602, 604-608, 610-611)
# ---------------------------------------------------------------------------
@given("I have a Settings instance with openai_api_key set to a secret value")
def step_create_settings_with_secret(context):
    """Create a Settings instance that has a sensitive key populated."""
    _save_and_clear_provider_env(context)
    os.environ["OPENAI_API_KEY"] = "sk-test-secret-12345"
    os.environ["CLEVERAGENTS_ENV"] = "test"
    os.environ["CLEVERAGENTS_MOCK_PROVIDERS"] = "true"
    try:
        context.settings_repr_target = Settings()
    finally:
        os.environ.pop("OPENAI_API_KEY", None)
        os.environ.pop("CLEVERAGENTS_ENV", None)
        os.environ.pop("CLEVERAGENTS_MOCK_PROVIDERS", None)
        _restore_provider_env(context)


@when("I call repr on the settings instance")
def step_call_repr(context):
    """Invoke repr() on the stored Settings instance."""
    context.settings_repr_output = repr(context.settings_repr_target)


@then("the repr output should contain REDACTED for the openai_api_key field")
def step_check_repr_redacted(context):
    """Verify the sensitive field is masked."""
    expected = f"openai_api_key={REDACTED!r}"
    assert expected in context.settings_repr_output, (
        f"Expected '{expected}' in repr output:\n{context.settings_repr_output}"
    )


@then('the repr output should start with "Settings(" and end with ")"')
def step_check_repr_format(context):
    """Verify overall repr format."""
    assert context.settings_repr_output.startswith("Settings("), (
        f"Expected repr to start with 'Settings(': {context.settings_repr_output[:50]}"
    )
    assert context.settings_repr_output.endswith(")"), (
        f"Expected repr to end with ')': {context.settings_repr_output[-50:]}"
    )


# ---------------------------------------------------------------------------
# Scenario: __repr__ shows non-sensitive fields normally
# (lines 604, 605, 606, 610, 611)
# ---------------------------------------------------------------------------
@given("I have a Settings instance with default values")
def step_create_settings_defaults(context):
    """Create a Settings instance with defaults (no secrets set)."""
    _save_and_clear_provider_env(context)
    os.environ["CLEVERAGENTS_ENV"] = "test"
    os.environ["CLEVERAGENTS_MOCK_PROVIDERS"] = "true"
    try:
        context.settings_repr_target = Settings()
    finally:
        os.environ.pop("CLEVERAGENTS_ENV", None)
        os.environ.pop("CLEVERAGENTS_MOCK_PROVIDERS", None)
        _restore_provider_env(context)


@then("the repr output should contain the env field with its actual value")
def step_check_repr_env_field(context):
    """Verify that non-sensitive fields are shown with their actual values."""
    assert "env='test'" in context.settings_repr_output, (
        f"Expected \"env='test'\" in repr output:\n{context.settings_repr_output}"
    )


@then("the repr output should not contain REDACTED for non-sensitive fields")
def step_check_repr_no_unnecessary_redaction(context):
    """Verify that the 'env' field (non-sensitive) is not redacted."""
    assert f"env={REDACTED!r}" not in context.settings_repr_output, (
        "Did not expect 'env' to be redacted"
    )


# ---------------------------------------------------------------------------
# Scenario: __repr__ shows None for sensitive keys that are None
# (lines 606, 607 false-branch, 610)
# ---------------------------------------------------------------------------
@given("I have a Settings instance where all sensitive keys are None")
def step_create_settings_no_secrets(context):
    """Create a Settings with no API keys set (all remain None)."""
    _save_and_clear_provider_env(context)
    os.environ["CLEVERAGENTS_ENV"] = "test"
    os.environ["CLEVERAGENTS_MOCK_PROVIDERS"] = "true"
    try:
        context.settings_repr_target = Settings()
    finally:
        os.environ.pop("CLEVERAGENTS_ENV", None)
        os.environ.pop("CLEVERAGENTS_MOCK_PROVIDERS", None)
        _restore_provider_env(context)


@then("the repr output should show None for sensitive key fields without redaction")
def step_check_repr_none_not_redacted(context):
    """When a sensitive key is None, it should show None rather than REDACTED."""
    output = context.settings_repr_output
    # azure_api_key defaults to None and should NOT be redacted
    assert "azure_api_key=None" in output, (
        f"Expected 'azure_api_key=None' in repr output:\n{output}"
    )
    # Confirm REDACTED is NOT used for a None-valued sensitive key
    assert f"azure_api_key={REDACTED!r}" not in output, (
        "Did not expect azure_api_key to be redacted when its value is None"
    )
