"""
Tests for brynq.key_manager — Per-user API key vault (BYOK).

Covers: store, retrieve, encryption roundtrip, list with masking,
delete, invalid user, provider validation.
"""

import json
import os
import sys
import tempfile
from pathlib import Path
from unittest import mock

import pytest

# Ensure the source tree is importable
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))

from brynq.key_manager import (
    KEYS_DIR,
    VALID_PROVIDERS,
    _mask_key,
    _user_key_path,
    delete_user_key,
    get_user_key,
    list_user_keys,
    store_user_key,
)


@pytest.fixture(autouse=True)
def _isolated_keys_dir(tmp_path, monkeypatch):
    """Redirect KEYS_DIR to a temp directory for every test."""
    test_keys_dir = tmp_path / "keys"
    test_keys_dir.mkdir()
    monkeypatch.setattr("brynq.key_manager.KEYS_DIR", test_keys_dir)
    yield test_keys_dir


# ── Store and retrieve ───────────────────────────────────────────────────


class TestStoreAndRetrieve:
    def test_store_and_get_roundtrip(self):
        """Store a key, then retrieve it — should get back the exact same string."""
        api_key = "sk-ant-api03-ABCDEF1234567890abcdef"
        result = store_user_key("user42", "claude", api_key)
        assert result["ok"] is True
        assert result["provider"] == "claude"
        assert "..." in result["masked_key"]  # masked, not raw

        retrieved = get_user_key("user42", "claude")
        assert retrieved == api_key

    def test_store_overwrites_previous(self):
        """Storing a new key for the same provider replaces the old one."""
        store_user_key("user42", "gemini", "first-key-value-here")
        store_user_key("user42", "gemini", "second-key-value-here")
        assert get_user_key("user42", "gemini") == "second-key-value-here"

    def test_get_nonexistent_provider(self):
        """Getting a key for a provider that was never stored returns None."""
        store_user_key("user42", "claude", "sk-some-key-value-1234")
        assert get_user_key("user42", "openai") is None

    def test_get_nonexistent_user(self):
        """Getting a key for a user that doesn't exist returns None."""
        assert get_user_key("ghost-user", "claude") is None

    def test_multiple_providers_per_user(self):
        """A user can store keys for multiple providers independently."""
        store_user_key("user42", "claude", "claude-key-abcd1234")
        store_user_key("user42", "openai", "openai-key-efgh5678")
        store_user_key("user42", "gemini", "gemini-key-ijkl9012")

        assert get_user_key("user42", "claude") == "claude-key-abcd1234"
        assert get_user_key("user42", "openai") == "openai-key-efgh5678"
        assert get_user_key("user42", "gemini") == "gemini-key-ijkl9012"

    def test_different_users_isolated(self):
        """Keys for different users are stored in separate files."""
        store_user_key("alice", "claude", "alice-claude-key-1234")
        store_user_key("bob", "claude", "bob-claude-key-5678")

        assert get_user_key("alice", "claude") == "alice-claude-key-1234"
        assert get_user_key("bob", "claude") == "bob-claude-key-5678"


# ── Encryption at rest ───────────────────────────────────────────────────


class TestEncryptionAtRest:
    def test_key_file_does_not_contain_plaintext(self, _isolated_keys_dir):
        """The stored JSON file must NOT contain the raw API key."""
        raw_key = "sk-ant-api03-THIS-IS-A-SECRET-KEY-VALUE"
        store_user_key("user42", "claude", raw_key)

        key_file = _isolated_keys_dir / "user42.json"
        assert key_file.exists()
        file_contents = key_file.read_text("utf-8")
        assert raw_key not in file_contents

    def test_stored_data_is_json(self, _isolated_keys_dir):
        """The key file should be valid JSON with a 'keys' dict."""
        store_user_key("user42", "claude", "sk-some-key-value-1234")
        key_file = _isolated_keys_dir / "user42.json"
        data = json.loads(key_file.read_text("utf-8"))
        assert "keys" in data
        assert "claude" in data["keys"]
        # The value should be a base64-encoded encrypted string, not the raw key
        assert data["keys"]["claude"] != "sk-some-key-value-1234"


# ── List with masking ────────────────────────────────────────────────────


class TestListWithMasking:
    def test_list_empty_user(self):
        """Listing keys for a user with no stored keys returns empty list."""
        assert list_user_keys("nobody") == []

    def test_list_returns_masked_keys(self):
        """Listed keys show provider names and masked (not raw) key values."""
        store_user_key("user42", "claude", "sk-ant-api03-ABCDEF1234567890")
        store_user_key("user42", "openai", "sk-proj-XYZW9876543210abcd")

        keys = list_user_keys("user42")
        assert len(keys) == 2

        providers = {k["provider"] for k in keys}
        assert providers == {"claude", "openai"}

        for entry in keys:
            assert "..." in entry["masked_key"]
            # Must not contain the full key
            assert len(entry["masked_key"]) < 30

    def test_list_sorted_by_provider(self):
        """Listed keys are sorted alphabetically by provider."""
        store_user_key("user42", "openai", "openai-key-abcd1234")
        store_user_key("user42", "claude", "claude-key-efgh5678")
        store_user_key("user42", "gemini", "gemini-key-ijkl9012")

        keys = list_user_keys("user42")
        providers = [k["provider"] for k in keys]
        assert providers == ["claude", "gemini", "openai"]


# ── Delete ───────────────────────────────────────────────────────────────


class TestDeleteKey:
    def test_delete_existing_key(self):
        """Deleting an existing key removes it and returns ok."""
        store_user_key("user42", "claude", "sk-some-key-value-1234")
        result = delete_user_key("user42", "claude")
        assert result["ok"] is True
        assert get_user_key("user42", "claude") is None

    def test_delete_nonexistent_key(self):
        """Deleting a key that doesn't exist returns an error."""
        result = delete_user_key("user42", "claude")
        assert result["ok"] is False
        assert "error" in result

    def test_delete_preserves_other_keys(self):
        """Deleting one provider's key doesn't affect others."""
        store_user_key("user42", "claude", "claude-key-abcd1234")
        store_user_key("user42", "openai", "openai-key-efgh5678")

        delete_user_key("user42", "claude")

        assert get_user_key("user42", "claude") is None
        assert get_user_key("user42", "openai") == "openai-key-efgh5678"


# ── Validation ───────────────────────────────────────────────────────────


class TestValidation:
    def test_invalid_provider_rejected(self):
        """Storing a key for an unknown provider returns an error."""
        result = store_user_key("user42", "not_a_provider", "sk-some-key-1234")
        assert result["ok"] is False
        assert "Unknown provider" in result["error"]

    def test_empty_key_rejected(self):
        """Storing an empty key returns an error."""
        result = store_user_key("user42", "claude", "")
        assert result["ok"] is False
        assert "empty" in result["error"].lower()

    def test_too_short_key_rejected(self):
        """Storing a key shorter than 8 chars returns an error."""
        result = store_user_key("user42", "claude", "short")
        assert result["ok"] is False
        assert "short" in result["error"].lower()

    def test_whitespace_trimmed(self):
        """Leading/trailing whitespace is trimmed from provider and key."""
        result = store_user_key("user42", "  Claude  ", "  sk-ant-api03-ABCDEF1234  ")
        assert result["ok"] is True
        assert result["provider"] == "claude"
        assert get_user_key("user42", "claude") == "sk-ant-api03-ABCDEF1234"

    def test_invalid_user_id_rejected(self):
        """A user_id with only special characters raises ValueError."""
        with pytest.raises(ValueError, match="Invalid user_id"):
            store_user_key("../../../", "claude", "sk-some-key-1234")


# ── Masking helper ───────────────────────────────────────────────────────


class TestMaskKey:
    def test_long_key_masked(self):
        assert _mask_key("sk-ant-api03-ABCDEF1234567890") == "sk-a...7890"

    def test_medium_key_masked(self):
        """Keys of length 7-8 use 3-char prefix/suffix."""
        assert _mask_key("abcdefgh") == "abc...fgh"

    def test_short_key_masked(self):
        """Keys of length 7 use 3-char prefix/suffix."""
        assert _mask_key("abcdefg") == "abc...efg"

    def test_tiny_key_hidden(self):
        """Keys of 6 chars or less are fully masked."""
        assert _mask_key("abc") == "***"
        assert _mask_key("abcdef") == "***"

    def test_nine_char_key_uses_four(self):
        """Keys of length 9+ use 4-char prefix/suffix."""
        assert _mask_key("123456789") == "1234...6789"
