"""
Unit tests for resolve_api_key_with_fallback function.

Tests verify the public API of lattice.shell.config.resolve_api_key_with_fallback
using file isolation via tmp_path and monkeypatch fixtures.

Per R7 (Boundary Isolation): Unit tests mock the filesystem via monkeypatch.

Specification:
- Priority chain: config_key > auth.json > env_var > None
- Variable syntax in config_key is resolved via resolve_api_key
- Provider lookup in auth.json
- Direct env var fallback
- All sources missing returns Success(None)
"""

import pytest
from returns.result import Failure, Success

from lattice.shell.config import resolve_api_key_with_fallback


class TestPriorityChain:
    """Test the priority chain: config > auth.json > env > None."""

    def test_priority_config_over_auth(self, monkeypatch, tmp_path):
        """When config_key is set, it takes priority over auth.json."""
        auth_file = tmp_path / "auth.json"
        auth_file.write_text('{"openai": {"api_key": "auth-key"}}')
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = resolve_api_key_with_fallback("sk-config", "openai")
        assert isinstance(result, Success), f"Expected Success, got {result}"
        assert result.unwrap() == "sk-config"

    def test_priority_config_over_env(self, monkeypatch, tmp_path):
        """When config_key is set, it takes priority over env var."""
        monkeypatch.setenv("OPENAI_API_KEY", "env-key")

        result = resolve_api_key_with_fallback("sk-config", "openai", "OPENAI_API_KEY")
        assert isinstance(result, Success), f"Expected Success, got {result}"
        assert result.unwrap() == "sk-config"

    def test_priority_auth_over_env(self, monkeypatch, tmp_path):
        """When config_key is None, auth.json takes priority over env."""
        auth_file = tmp_path / "auth.json"
        auth_file.write_text('{"openai": {"api_key": "auth-key"}}')
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)
        monkeypatch.setenv("OPENAI_API_KEY", "env-key")

        result = resolve_api_key_with_fallback(None, "openai", "OPENAI_API_KEY")
        assert isinstance(result, Success), f"Expected Success, got {result}"
        assert result.unwrap() == "auth-key"

    def test_priority_env_over_none(self, monkeypatch, tmp_path):
        """When config and auth are missing, env takes priority."""
        auth_file = tmp_path / "auth.json"
        auth_file.write_text("{}")  # Empty auth
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)
        monkeypatch.setenv("OPENAI_API_KEY", "env-key")

        result = resolve_api_key_with_fallback(None, "openai", "OPENAI_API_KEY")
        assert isinstance(result, Success), f"Expected Success, got {result}"
        assert result.unwrap() == "env-key"

    def test_all_missing_returns_none(self, monkeypatch, tmp_path):
        """When all sources are missing, returns Success(None)."""
        auth_file = tmp_path / "auth.json"
        auth_file.write_text("{}")
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)
        monkeypatch.delenv("OPENAI_API_KEY", raising=False)

        result = resolve_api_key_with_fallback(None, "openai", "OPENAI_API_KEY")
        assert isinstance(result, Success), f"Expected Success, got {result}"
        assert result.unwrap() is None


class TestVariableResolutionInConfigKey:
    """Test variable syntax resolution in config_key."""

    def test_variable_syntax_in_config_key(self, monkeypatch, tmp_path):
        """Variable syntax {env:VAR} in config_key is resolved."""
        monkeypatch.setenv("MY_SECRET_KEY", "resolved-key")

        result = resolve_api_key_with_fallback("{env:MY_SECRET_KEY}", "openai")
        assert isinstance(result, Success), f"Expected Success, got {result}"
        assert result.unwrap() == "resolved-key"

    def test_variable_syntax_takes_priority_over_auth(self, monkeypatch, tmp_path):
        """Variable syntax in config_key takes priority over auth.json."""
        auth_file = tmp_path / "auth.json"
        auth_file.write_text('{"openai": {"api_key": "auth-key"}}')
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)
        monkeypatch.setenv("MY_SECRET_KEY", "resolved-key")

        result = resolve_api_key_with_fallback("{env:MY_SECRET_KEY}", "openai")
        assert isinstance(result, Success), f"Expected Success, got {result}"
        assert result.unwrap() == "resolved-key"

    def test_file_variable_in_config_key(self, monkeypatch, tmp_path):
        """File variable syntax {file:/path} in config_key is resolved."""
        key_file = tmp_path / "secret.txt"
        key_file.write_text("file-based-key")

        result = resolve_api_key_with_fallback(f"{{file:{key_file}}}", "openai")
        assert isinstance(result, Success), f"Expected Success, got {result}"
        assert result.unwrap() == "file-based-key"


class TestAuthProviderLookup:
    """Test provider lookup in auth.json."""

    def test_auth_provider_lookup(self, monkeypatch, tmp_path):
        """Provider lookup in auth.json works."""
        auth_file = tmp_path / "auth.json"
        auth_file.write_text('{"anthropic": {"api_key": "sk-ant-test"}}')
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = resolve_api_key_with_fallback(None, "anthropic")
        assert isinstance(result, Success), f"Expected Success, got {result}"
        assert result.unwrap() == "sk-ant-test"

    def test_auth_multiple_providers(self, monkeypatch, tmp_path):
        """Different providers return different keys from auth.json."""
        auth_file = tmp_path / "auth.json"
        auth_file.write_text(
            '{"openai": {"api_key": "sk-openai"}, "anthropic": {"api_key": "sk-anthropic"}}'
        )
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result_openai = resolve_api_key_with_fallback(None, "openai")
        result_anthropic = resolve_api_key_with_fallback(None, "anthropic")

        assert isinstance(result_openai, Success)
        assert result_openai.unwrap() == "sk-openai"

        assert isinstance(result_anthropic, Success)
        assert result_anthropic.unwrap() == "sk-anthropic"

    def test_auth_missing_provider_falls_back_to_env(self, monkeypatch, tmp_path):
        """When provider not in auth.json, falls back to env var."""
        auth_file = tmp_path / "auth.json"
        auth_file.write_text('{"openai": {"api_key": "sk-openai"}}')
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)
        monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-anthropic-env")

        # Requesting 'anthropic' which is not in auth.json
        result = resolve_api_key_with_fallback(None, "anthropic", "ANTHROPIC_API_KEY")
        assert isinstance(result, Success), f"Expected Success, got {result}"
        assert result.unwrap() == "sk-anthropic-env"


class TestEnvVarFallback:
    """Test direct env var fallback."""

    def test_env_var_fallback(self, monkeypatch, tmp_path):
        """Direct env var fallback works when config and auth are missing."""
        auth_file = tmp_path / "auth.json"
        auth_file.write_text("{}")
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)
        monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-env")

        result = resolve_api_key_with_fallback(
            None, "other-provider", "ANTHROPIC_API_KEY"
        )
        assert isinstance(result, Success), f"Expected Success, got {result}"
        assert result.unwrap() == "sk-ant-env"

    def test_env_var_none_parameter(self, monkeypatch, tmp_path):
        """When env_var is None, no env lookup occurs."""
        auth_file = tmp_path / "auth.json"
        auth_file.write_text("{}")
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)
        monkeypatch.setenv("OPENAI_API_KEY", "env-key")

        # env_var=None skips env lookup
        result = resolve_api_key_with_fallback(None, "openai", None)
        assert isinstance(result, Success), f"Expected Success, got {result}"
        assert result.unwrap() is None

    def test_env_var_missing_returns_none(self, monkeypatch, tmp_path):
        """When env var is not set, returns Success(None)."""
        auth_file = tmp_path / "auth.json"
        auth_file.write_text("{}")
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)
        monkeypatch.delenv("MISSING_API_KEY", raising=False)

        result = resolve_api_key_with_fallback(None, "provider", "MISSING_API_KEY")
        assert isinstance(result, Success), f"Expected Success, got {result}"
        assert result.unwrap() is None


class TestEdgeCases:
    """Test edge cases and boundary conditions."""

    def test_empty_config_key_uses_auth(self, monkeypatch, tmp_path):
        """Empty string config_key should NOT take priority (None vs '')."""
        # Empty string is a valid key but falsy - spec check
        auth_file = tmp_path / "auth.json"
        auth_file.write_text('{"openai": {"api_key": "auth-key"}}')
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = resolve_api_key_with_fallback("", "openai", "OPENAI_API_KEY")
        # Empty string is still a value, should be returned
        assert isinstance(result, Success), f"Expected Success, got {result}"
        assert result.unwrap() == ""

    def test_provider_not_in_auth_no_env(self, monkeypatch, tmp_path):
        """Provider not in auth and no env var returns Success(None)."""
        auth_file = tmp_path / "auth.json"
        auth_file.write_text('{"openai": {"api_key": "sk-openai"}}')
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = resolve_api_key_with_fallback(None, "unknown-provider")
        assert isinstance(result, Success), f"Expected Success, got {result}"
        assert result.unwrap() is None

    def test_config_failure_propagates(self, monkeypatch, tmp_path):
        """Invalid variable syntax in config_key returns Failure."""
        # Invalid syntax like {env:} should return Failure
        result = resolve_api_key_with_fallback("{env:}", "openai")
        assert isinstance(result, Failure), (
            f"Expected Failure for invalid syntax, got {result}"
        )
