"""Integration tests for auth key resolution (RFC-XXX).

These are L2 integration tests verifying the complete key resolution flow:
- config.py: resolve_api_key(), resolve_api_key_with_fallback()
- auth.py: save_auth(), load_auth(), get_auth(), delete_auth()
- CompilerConfig: api_key field

Priority chain (RFC-XXX):
1. config_key (with variable resolution via resolve_api_key)
2. auth.json for provider
3. env_var (direct environment variable lookup)
4. None (litellm default handling)
"""

import json
import os
from pathlib import Path

import pytest
from returns.result import Failure, Success

from lattice.core.types.config import CompilerConfig
from lattice.shell.auth import (
    AUTH_FILE,
    delete_auth,
    get_auth,
    get_auth_file_path,
    load_auth,
    save_auth,
)
from lattice.shell.config import resolve_api_key, resolve_api_key_with_fallback


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


@pytest.fixture
def isolated_auth_file(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
    """Create an isolated auth.json file for each test."""
    auth_file = tmp_path / "auth.json"
    # Patch the module-level AUTH_FILE constant
    import lattice.shell.auth as auth_module

    monkeypatch.setattr(auth_module, "AUTH_FILE", auth_file)
    return auth_file


# =============================================================================
# Full Key Resolution Integration Tests
# =============================================================================


class TestFullKeyResolutionIntegration:
    """Integration tests for the complete key resolution flow."""

    def test_config_to_llm_flow(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Config api_key flows through to LLM resolution."""
        # Set up config with api_key (model and api_key_env are required)
        config = CompilerConfig(
            model="openai/gpt-4", api_key_env="OPENAI_API_KEY", api_key="sk-from-config"
        )

        # Resolve through fallback chain
        result = resolve_api_key_with_fallback(
            config_key=config.api_key, provider="openai", env_var=None
        )

        assert isinstance(result, Success)
        assert result.unwrap() == "sk-from-config"

    def test_auth_json_resolution(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Key from auth.json is used when config has no key."""
        # Save to auth.json
        result = save_auth("openai", "sk-from-auth")
        assert isinstance(result, Success)

        # Resolve with None config_key
        result = resolve_api_key_with_fallback(
            config_key=None, provider="openai", env_var=None
        )
        assert isinstance(result, Success)
        assert result.unwrap() == "sk-from-auth"

    def test_env_var_resolution(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Env var is used when config and auth.json have no key."""
        # Set environment variable
        monkeypatch.setenv("OPENAI_API_KEY", "sk-from-env")

        # Resolve with None config_key, empty auth, and env var
        result = resolve_api_key_with_fallback(
            config_key=None, provider="openai", env_var="OPENAI_API_KEY"
        )
        assert isinstance(result, Success)
        assert result.unwrap() == "sk-from-env"

    def test_priority_chain(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Priority: config > auth.json > env > None."""
        # Set up multiple sources
        save_auth("openai", "sk-from-auth").unwrap()
        monkeypatch.setenv("OPENAI_API_KEY", "sk-from-env")

        # 1. Config wins over everything
        result = resolve_api_key_with_fallback(
            config_key="sk-from-config", provider="openai", env_var="OPENAI_API_KEY"
        )
        assert isinstance(result, Success)
        assert result.unwrap() == "sk-from-config"

        # 2. Auth wins over env
        result = resolve_api_key_with_fallback(
            config_key=None, provider="openai", env_var="OPENAI_API_KEY"
        )
        assert isinstance(result, Success)
        assert result.unwrap() == "sk-from-auth"

        # 3. Env wins when auth is cleared
        delete_auth("openai").unwrap()
        result = resolve_api_key_with_fallback(
            config_key=None, provider="openai", env_var="OPENAI_API_KEY"
        )
        assert isinstance(result, Success)
        assert result.unwrap() == "sk-from-env"

        # 4. None when everything is empty
        monkeypatch.delenv("OPENAI_API_KEY")
        result = resolve_api_key_with_fallback(
            config_key=None, provider="openai", env_var="OPENAI_API_KEY"
        )
        assert isinstance(result, Success)
        assert result.unwrap() is None

    def test_auth_json_update_reflected(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Updates to auth.json are immediately reflected in resolution."""
        # Save initial key
        save_auth("anthropic", "sk-ant-initial").unwrap()

        # Resolve - should get initial key
        result = resolve_api_key_with_fallback(
            config_key=None, provider="anthropic", env_var=None
        )
        assert isinstance(result, Success)
        assert result.unwrap() == "sk-ant-initial"

        # Update the key
        save_auth("anthropic", "sk-ant-updated").unwrap()

        # Resolve again - should get updated key
        result = resolve_api_key_with_fallback(
            config_key=None, provider="anthropic", env_var=None
        )
        assert isinstance(result, Success)
        assert result.unwrap() == "sk-ant-updated"

    def test_multiple_providers(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Different providers can have different keys."""
        save_auth("openai", "sk-openai").unwrap()
        save_auth("anthropic", "sk-ant-anthropic").unwrap()
        save_auth("google", "sk-google").unwrap()

        # Verify each provider gets its own key
        result1 = resolve_api_key_with_fallback(
            config_key=None, provider="openai", env_var=None
        )
        assert result1.unwrap() == "sk-openai"

        result2 = resolve_api_key_with_fallback(
            config_key=None, provider="anthropic", env_var=None
        )
        assert result2.unwrap() == "sk-ant-anthropic"

        result3 = resolve_api_key_with_fallback(
            config_key=None, provider="google", env_var=None
        )
        assert result3.unwrap() == "sk-google"

    def test_variable_syntax_integration(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Variable syntax in config_key works end-to-end."""
        # Test {env:VAR} syntax
        monkeypatch.setenv("MY_SECRET_KEY", "sk-from-secret-env")
        result = resolve_api_key_with_fallback(
            config_key="{env:MY_SECRET_KEY}", provider="openai", env_var=None
        )
        assert isinstance(result, Success)
        assert result.unwrap() == "sk-from-secret-env"

        # Test {auth:provider} syntax
        save_auth("openai", "sk-from-auth").unwrap()
        result = resolve_api_key_with_fallback(
            config_key="{auth:openai}", provider="other", env_var=None
        )
        assert isinstance(result, Success)
        assert result.unwrap() == "sk-from-auth"


# =============================================================================
# Auth CRUD Integration Tests
# =============================================================================


class TestAuthCRUDIntegration:
    """Integration tests for auth.json CRUD operations."""

    def test_save_creates_file_if_not_exists(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """save_auth creates auth.json if it doesn't exist."""
        assert not isolated_auth_file.exists()

        result = save_auth("openai", "sk-test-123")
        assert isinstance(result, Success)

        assert isolated_auth_file.exists()
        data = json.loads(isolated_auth_file.read_text())
        assert data["openai"]["api_key"] == "sk-test-123"

    def test_save_updates_existing_entry(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """save_auth updates an existing provider entry."""
        # First save
        save_auth("openai", "sk-old").unwrap()
        # Second save should update
        save_auth("openai", "sk-new").unwrap()

        result = get_auth("openai")
        assert isinstance(result, Success)
        assert result.unwrap() == "sk-new"

    def test_save_sets_file_permissions(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """save_auth sets file permissions to 0o600."""
        save_auth("openai", "sk-test").unwrap()

        import stat

        file_stat = isolated_auth_file.stat()
        assert stat.S_IMODE(file_stat.st_mode) == 0o600

    def test_load_returns_empty_dict_if_no_file(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """load_auth returns empty dict if auth.json doesn't exist."""
        assert not isolated_auth_file.exists()

        result = load_auth()
        assert isinstance(result, Success)
        assert result.unwrap() == {}

    def test_load_returns_stored_data(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """load_auth returns all stored auth data."""
        save_auth("openai", "sk-openai").unwrap()
        save_auth("anthropic", "sk-anthropic").unwrap()

        result = load_auth()
        assert isinstance(result, Success)
        data = result.unwrap()
        assert "openai" in data
        assert "anthropic" in data

    def test_get_returns_none_for_unknown_provider(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """get_auth returns None for unknown provider."""
        result = get_auth("unknown_provider")
        assert isinstance(result, Success)
        assert result.unwrap() is None

    def test_delete_removes_provider(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """delete_auth removes a provider entry."""
        save_auth("openai", "sk-test").unwrap()

        # Delete should return True (was found)
        result = delete_auth("openai")
        assert isinstance(result, Success)
        assert result.unwrap() is True

        # Provider should be gone
        result = get_auth("openai")
        assert isinstance(result, Success)
        assert result.unwrap() is None

    def test_delete_returns_false_for_unknown_provider(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """delete_auth returns False if provider not found."""
        result = delete_auth("unknown_provider")
        assert isinstance(result, Success)
        assert result.unwrap() is False


# =============================================================================
# resolve_api_key Unit-like Integration Tests
# =============================================================================


class TestResolveApiKeyUnitIntegration:
    """Tests for resolve_api_key variable syntax resolution."""

    def test_resolve_none_returns_none(self) -> None:
        """resolve_api_key(None) returns Success(None)."""
        result = resolve_api_key(None)
        assert isinstance(result, Success)
        assert result.unwrap() is None

    def test_resolve_direct_key_returns_key(self) -> None:
        """resolve_api_key with direct key returns it unchanged."""
        result = resolve_api_key("sk-direct-key")
        assert isinstance(result, Success)
        assert result.unwrap() == "sk-direct-key"

    def test_resolve_env_syntax_resolves_from_env(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """resolve_api_key('{env:VAR}') resolves from environment."""
        monkeypatch.setenv("MY_API_KEY", "sk-from-my-env")

        result = resolve_api_key("{env:MY_API_KEY}")
        assert isinstance(result, Success)
        assert result.unwrap() == "sk-from-my-env"

    def test_resolve_env_syntax_missing_var_returns_failure(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """resolve_api_key('{env:VAR}') returns Failure for missing var."""
        monkeypatch.delenv("NONEXISTENT_VAR", raising=False)

        result = resolve_api_key("{env:NONEXISTENT_VAR}")
        assert isinstance(result, Failure)

    def test_resolve_auth_syntax_resolves_from_auth_json(
        self, isolated_auth_file: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """resolve_api_key('{auth:provider}') resolves from auth.json."""
        save_auth("openai", "sk-from-auth-store").unwrap()

        result = resolve_api_key("{auth:openai}")
        assert isinstance(result, Success)
        assert result.unwrap() == "sk-from-auth-store"

    def test_resolve_file_syntax_reads_file(
        self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """resolve_api_key('{file:/path}') reads key from file."""
        key_file = tmp_path / "api_key.txt"
        key_file.write_text("sk-from-file\n")

        result = resolve_api_key(f"{{file:{key_file}}}")
        assert isinstance(result, Success)
        assert result.unwrap() == "sk-from-file"
