import pytest

from app.services.api_key_manager import APIKeyConfig, APIKeyManager, KeySource, KeyStatus


class TestAPIKeyConfig:
    def test_config_creation(self):
        config = APIKeyConfig(
            engine_type="chatgpt",
            key_source=KeySource.SYSTEM,
            encrypted_key="ZW5jcnlwdGVk",
            key_hint="sk-...abc",
            status=KeyStatus.UNKNOWN,
            priority=0,
        )
        assert config.engine_type == "chatgpt"
        assert config.key_source == KeySource.SYSTEM
        assert config.encrypted_key == "ZW5jcnlwdGVk"
        assert config.key_hint == "sk-...abc"
        assert config.status == KeyStatus.UNKNOWN
        assert config.priority == 0

    def test_config_default_values(self):
        config = APIKeyConfig(
            engine_type="chatgpt",
            key_source=KeySource.USER,
            encrypted_key="abc",
            key_hint="***",
        )
        assert config.status == KeyStatus.UNKNOWN
        assert config.priority == 0
        assert config.last_verified_at is None
        assert config.created_at is None
        assert config.user_id is None

    def test_key_source_enum_values(self):
        assert KeySource.SYSTEM == "system"
        assert KeySource.USER == "user"
        assert KeySource.ENV == "env"

    def test_key_status_enum_values(self):
        assert KeyStatus.ACTIVE == "active"
        assert KeyStatus.INVALID == "invalid"
        assert KeyStatus.EXPIRED == "expired"
        assert KeyStatus.RATE_LIMITED == "rate_limited"
        assert KeyStatus.UNKNOWN == "unknown"


class TestAPIKeyManagerAddKey:
    @pytest.fixture
    def manager(self):
        return APIKeyManager()

    def test_add_system_key(self, manager):
        config = manager.add_key("chatgpt", "sk-1234567890abcdef", source=KeySource.SYSTEM)
        assert config.engine_type == "chatgpt"
        assert config.key_source == KeySource.SYSTEM
        assert config.encrypted_key != "sk-1234567890abcdef"
        assert config.key_hint == "sk-...def"

    def test_add_user_key(self, manager):
        config = manager.add_key("chatgpt", "sk-user1234567890", source=KeySource.USER, user_id="user_001")
        assert config.key_source == KeySource.USER
        assert config.user_id == "user_001"

    def test_add_multiple_keys_for_same_engine(self, manager):
        manager.add_key("chatgpt", "sk-1111111111111111", source=KeySource.SYSTEM)
        manager.add_key("chatgpt", "sk-2222222222222222", source=KeySource.USER, user_id="user_001")
        keys = manager.list_keys("chatgpt")
        assert len(keys) == 2


class TestAPIKeyManagerGetKey:
    @pytest.fixture
    def manager(self):
        mgr = APIKeyManager()
        mgr.add_key("chatgpt", "sk-system1234567890", source=KeySource.SYSTEM, priority=0)
        mgr.add_key("chatgpt", "sk-user1234567890ab", source=KeySource.USER, user_id="user_001", priority=10)
        return mgr

    def test_get_key_decrypts_correctly(self, manager):
        key = manager.get_key("chatgpt", user_id="user_001")
        assert key == "sk-user1234567890ab"

    def test_get_key_returns_system_key_when_no_user_id(self, manager):
        key = manager.get_key("chatgpt")
        assert key == "sk-system1234567890"

    def test_get_key_returns_none_for_unknown_engine(self, manager):
        key = manager.get_key("nonexistent")
        assert key is None

    def test_get_key_returns_none_for_wrong_user(self, manager):
        key = manager.get_key("chatgpt", user_id="user_999")
        assert key == "sk-system1234567890"


class TestAPIKeyManagerRemoveKey:
    @pytest.fixture
    def manager(self):
        mgr = APIKeyManager()
        mgr.add_key("chatgpt", "sk-1234567890abcdef", source=KeySource.SYSTEM)
        return mgr

    def test_remove_existing_key(self, manager):
        keys = manager.list_keys("chatgpt")
        hint = keys[0].key_hint
        result = manager.remove_key("chatgpt", hint)
        assert result is True
        assert len(manager.list_keys("chatgpt")) == 0

    def test_remove_nonexistent_key(self, manager):
        result = manager.remove_key("chatgpt", "sk-...xyz")
        assert result is False


class TestAPIKeyManagerListKeys:
    @pytest.fixture
    def manager(self):
        mgr = APIKeyManager()
        mgr.add_key("chatgpt", "sk-chatgpt12345678", source=KeySource.SYSTEM)
        mgr.add_key("perplexity", "sk-perplexity12345", source=KeySource.SYSTEM)
        return mgr

    def test_list_keys_for_engine(self, manager):
        keys = manager.list_keys("chatgpt")
        assert len(keys) == 1
        assert keys[0].engine_type == "chatgpt"

    def test_list_all_keys(self, manager):
        keys = manager.list_keys()
        assert len(keys) == 2

    def test_list_keys_returns_masked_hints_only(self, manager):
        keys = manager.list_keys("chatgpt")
        for k in keys:
            assert k.encrypted_key != "sk-chatgpt12345678"
            assert "..." in k.key_hint


class TestKeyEncryption:
    @pytest.fixture
    def manager(self):
        return APIKeyManager()

    def test_encrypted_key_is_not_plaintext(self, manager):
        original = "sk-my-super-secret-key-123456"
        config = manager.add_key("chatgpt", original, source=KeySource.SYSTEM)
        assert config.encrypted_key != original

    def test_encrypt_decrypt_roundtrip(self, manager):
        original = "sk-roundtrip-test-key-12345"
        manager.add_key("chatgpt", original, source=KeySource.SYSTEM)
        decrypted = manager.get_key("chatgpt")
        assert decrypted == original

    def test_key_hint_masks_middle(self, manager):
        config = manager.add_key("chatgpt", "sk-abcdefghijklmnop", source=KeySource.SYSTEM)
        assert config.key_hint.startswith("sk-")
        assert config.key_hint.endswith("nop")
        assert "..." in config.key_hint

    def test_short_key_hint(self, manager):
        config = manager.add_key("chatgpt", "short", source=KeySource.SYSTEM)
        assert config.key_hint == "***"


class TestKeyVerification:
    @pytest.fixture
    def manager(self):
        return APIKeyManager()

    @pytest.mark.asyncio
    async def test_verify_calls_factory(self, manager):
        from unittest.mock import patch, AsyncMock

        with patch('app.services.api_key_manager.KeyVerifierFactory.verify', new_callable=AsyncMock) as mock_verify:
            mock_verify.return_value = KeyStatus.ACTIVE
            status = await manager.verify_key("chatgpt", "sk-valid-key-1234567890")
            assert status == KeyStatus.ACTIVE
            mock_verify.assert_called_once_with("chatgpt", "sk-valid-key-1234567890")

    @pytest.mark.asyncio
    async def test_verify_empty_key(self, manager):
        status = await manager.verify_key("chatgpt", "")
        assert status == KeyStatus.INVALID

    @pytest.mark.asyncio
    async def test_verify_short_key(self, manager):
        status = await manager.verify_key("chatgpt", "short")
        assert status == KeyStatus.INVALID


class TestKeyPriority:
    @pytest.fixture
    def manager(self):
        mgr = APIKeyManager()
        mgr.add_key("chatgpt", "sk-env-key-1234567890", source=KeySource.ENV, priority=0)
        mgr.add_key("chatgpt", "sk-system-key-1234567", source=KeySource.SYSTEM, priority=5)
        mgr.add_key("chatgpt", "sk-user-key-123456789", source=KeySource.USER, user_id="user_001", priority=10)
        return mgr

    def test_keys_sorted_by_priority(self, manager):
        keys = manager.list_keys("chatgpt")
        assert keys[0].priority == 10
        assert keys[1].priority == 5
        assert keys[2].priority == 0

    def test_user_key_has_higher_priority(self, manager):
        key = manager.get_key("chatgpt", user_id="user_001")
        assert key == "sk-user-key-123456789"

    def test_system_key_used_when_no_user_key(self, manager):
        key = manager.get_key("chatgpt", user_id="user_999")
        assert key == "sk-system-key-1234567"


class TestBestKeySelection:
    @pytest.fixture
    def manager(self):
        mgr = APIKeyManager()
        mgr.add_key("chatgpt", "sk-user-active-key-12", source=KeySource.USER, user_id="user_001", priority=10)
        mgr.add_key("chatgpt", "sk-system-active-key1", source=KeySource.SYSTEM, priority=5)
        mgr.add_key("chatgpt", "sk-env-active-key-1234", source=KeySource.ENV, priority=0)
        return mgr

    def test_get_best_key_prefers_user_key(self, manager):
        key = manager.get_key("chatgpt", user_id="user_001")
        assert key == "sk-user-active-key-12"

    def test_get_best_key_falls_back_to_system(self, manager):
        key = manager.get_key("chatgpt")
        assert key == "sk-system-active-key1"


class TestKeyDegradation:
    @pytest.fixture
    def manager(self):
        mgr = APIKeyManager()
        mgr.add_key("chatgpt", "sk-user-key-123456789", source=KeySource.USER, user_id="user_001", priority=10)
        mgr.add_key("chatgpt", "sk-system-key-1234567", source=KeySource.SYSTEM, priority=5)
        return mgr

    def test_degradation_to_system_key_when_user_key_invalid(self, manager):
        keys = manager.list_keys("chatgpt")
        for k in keys:
            if k.key_source == KeySource.USER and k.user_id == "user_001":
                k.status = KeyStatus.INVALID
        key = manager.get_key("chatgpt", user_id="user_001")
        assert key == "sk-system-key-1234567"

    def test_degradation_to_env_key_when_system_key_rate_limited(self, manager):
        manager.add_key("chatgpt", "sk-env-key-1234567890", source=KeySource.ENV, priority=0)
        keys = manager.list_keys("chatgpt")
        for k in keys:
            if k.key_source == KeySource.SYSTEM:
                k.status = KeyStatus.RATE_LIMITED
        key = manager.get_key("chatgpt")
        assert key == "sk-env-key-1234567890"

    def test_returns_none_when_all_keys_unavailable(self, manager):
        keys = manager.list_keys("chatgpt")
        for k in keys:
            k.status = KeyStatus.INVALID
        key = manager.get_key("chatgpt")
        assert key is None


class TestKeyExpiry:
    @pytest.fixture
    def manager(self):
        mgr = APIKeyManager()
        mgr.add_key("chatgpt", "sk-expired-key-1234567", source=KeySource.SYSTEM, priority=5)
        mgr.add_key("chatgpt", "sk-active-key-12345678", source=KeySource.ENV, priority=0)
        return mgr

    def test_expired_key_skipped(self, manager):
        keys = manager.list_keys("chatgpt")
        for k in keys:
            if k.key_source == KeySource.SYSTEM:
                k.status = KeyStatus.EXPIRED
        key = manager.get_key("chatgpt")
        assert key == "sk-active-key-12345678"

    def test_active_and_unknown_keys_are_usable(self, manager):
        keys = manager.list_keys("chatgpt")
        usable = [k for k in keys if k.status in (KeyStatus.ACTIVE, KeyStatus.UNKNOWN)]
        assert len(usable) == 2


class TestLoadEnvKeys:
    @pytest.fixture
    def manager(self):
        return APIKeyManager()

    def test_load_env_keys_from_environment(self, manager, monkeypatch):
        monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-from-env-1234567890")
        monkeypatch.setenv("DEEPSEEK_API_KEY", "sk-deepseek-from-env-12345678")
        manager.load_env_keys()
        keys = manager.list_keys()
        engine_types = {k.engine_type for k in keys}
        assert "chatgpt" in engine_types
        assert "deepseek" in engine_types

    def test_env_keys_have_env_source(self, manager, monkeypatch):
        monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-from-env-1234567890")
        manager.load_env_keys()
        keys = manager.list_keys("chatgpt")
        assert len(keys) == 1
        assert keys[0].key_source == KeySource.ENV

    def test_env_keys_not_loaded_when_empty(self, manager, monkeypatch):
        monkeypatch.delenv("OPENAI_API_KEY", raising=False)
        monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False)
        manager.load_env_keys()
        keys = manager.list_keys()
        assert len(keys) == 0
