"""
Tests for oaa.modules.params_or_env

Tests the params_or_env() function which retrieves configuration values
from environment variables, connection params, or source params.
"""

import os
import pytest
from oaa.modules.params_or_env import params_or_env


# ============================================================================
# Fixtures
# ============================================================================


@pytest.fixture
def mock_connection():
    """Mock connection config object with params dict."""

    class MockConfig:
        def __init__(self, params=None):
            self.params = params

    return MockConfig


@pytest.fixture
def mock_source():
    """Mock source config object with params dict."""

    class MockConfig:
        def __init__(self, params=None):
            self.params = params

    return MockConfig


@pytest.fixture(autouse=True)
def clean_env():
    """Clean up test environment variables after each test."""
    test_keys = []
    yield test_keys
    for key in test_keys:
        os.environ.pop(key, None)


# ============================================================================
# Lookup Order Tests
# ============================================================================


class TestLookupOrder:
    """Test the priority order: env > connection > source > default."""

    def test_env_has_highest_priority(self, mock_connection, mock_source, clean_env):
        """Environment variable should win over connection and source params."""
        key = "TEST_ENV_PRIORITY"
        clean_env.append(key)
        os.environ[key] = "from-env"

        connection = mock_connection({"TEST_ENV_PRIORITY": "from-connection"})
        source = mock_source({"TEST_ENV_PRIORITY": "from-source"})

        result = params_or_env(connection, source, key)
        assert result == "from-env"

    def test_connection_params_over_source(self, mock_connection, mock_source):
        """Connection params should win over source params."""
        connection = mock_connection({"API_KEY": "conn-value"})
        source = mock_source({"API_KEY": "source-value"})

        result = params_or_env(connection, source, "API_KEY")
        assert result == "conn-value"

    def test_source_params_used_as_fallback(self, mock_connection, mock_source):
        """Source params should be used when not in env or connection."""
        connection = mock_connection({"OTHER_KEY": "other"})
        source = mock_source({"API_KEY": "source-value"})

        result = params_or_env(connection, source, "API_KEY")
        assert result == "source-value"

    def test_default_used_when_not_found(self, mock_connection, mock_source):
        """Default value should be returned when key not found anywhere."""
        connection = mock_connection({"OTHER": "value"})
        source = mock_source({"ANOTHER": "value"})

        result = params_or_env(connection, source, "NOT_FOUND", default="fallback")
        assert result == "fallback"


# ============================================================================
# None Safety Tests
# ============================================================================


class TestNoneSafety:
    """Test that function handles None values gracefully."""

    def test_none_connection(self, mock_source):
        """Should work when connection is None."""
        source = mock_source({"API_KEY": "source-value"})

        result = params_or_env(None, source, "API_KEY")
        assert result == "source-value"

    def test_none_source(self, mock_connection):
        """Should work when source is None."""
        connection = mock_connection({"API_KEY": "conn-value"})

        result = params_or_env(connection, None, "API_KEY")
        assert result == "conn-value"

    def test_both_none(self):
        """Should work when both connection and source are None."""
        result = params_or_env(None, None, "NOT_FOUND", default="safe")
        assert result == "safe"

    def test_empty_params(self, mock_connection, mock_source):
        """Should work when params dict is empty."""
        connection = mock_connection({})
        source = mock_source({})

        result = params_or_env(connection, source, "NOT_FOUND", default="empty")
        assert result == "empty"

    def test_none_params(self, mock_connection, mock_source):
        """Should work when params attribute is None."""
        connection = mock_connection(None)
        source = mock_source(None)

        result = params_or_env(connection, source, "NOT_FOUND", default="none-params")
        assert result == "none-params"


# ============================================================================
# Default Parameter Tests
# ============================================================================


class TestDefaultParameter:
    """Test the default parameter behavior."""

    def test_default_none_implicit(self, mock_connection, mock_source):
        """Should return None when not found and no default specified."""
        connection = mock_connection({})
        source = mock_source({})

        result = params_or_env(connection, source, "NOT_FOUND")
        assert result is None

    def test_default_custom_value(self, mock_connection, mock_source):
        """Should return custom default when not found."""
        connection = mock_connection({})
        source = mock_source({})

        result = params_or_env(
            connection, source, "NOT_FOUND", default="custom-default"
        )
        assert result == "custom-default"

    def test_default_not_used_when_found(self, mock_connection, mock_source):
        """Default should be ignored when value is found."""
        connection = mock_connection({"KEY": "found-value"})
        source = mock_source({})

        result = params_or_env(connection, source, "KEY", default="ignored")
        assert result == "found-value"


# ============================================================================
# Edge Cases
# ============================================================================


class TestEdgeCases:
    """Test edge cases and unusual inputs."""

    def test_empty_string_env_var(self, mock_connection, mock_source, clean_env):
        """Empty string env var should still be returned (not treated as missing)."""
        key = "TEST_EMPTY_STRING"
        clean_env.append(key)
        os.environ[key] = ""

        connection = mock_connection({"TEST_EMPTY_STRING": "not-empty"})
        source = mock_source({})

        result = params_or_env(connection, source, key, default="default")
        assert result == ""

    def test_key_exists_with_none_value(self, mock_connection, mock_source):
        """Key with None value in params should return None."""
        connection = mock_connection({"KEY": None})
        source = mock_source({})

        result = params_or_env(connection, source, "KEY", default="default")
        assert result is None
