# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# Licensed under the MIT License

"""Tests for credential_store.py — encrypt/decrypt + unified keyring."""

import json
import os
from pathlib import Path
from unittest.mock import patch

import pytest

from familiar.core.credential_store import (
    _FERNET_PREFIX,
    decrypt_password,
    encrypt_password,
    keyring_delete,
    keyring_get,
    keyring_list,
    keyring_migrate_env,
    keyring_set,
)


# ── Encrypt / Decrypt roundtrip ─────────────────────────────────────────


class TestEncryptDecrypt:
    def test_roundtrip(self, tmp_path):
        """Encrypt then decrypt returns original plaintext."""
        key_file = tmp_path / "credentials.key"
        with patch("familiar.core.credential_store._key_file_override", key_file):
            encrypted = encrypt_password("my-secret-key")
            assert encrypted.startswith(_FERNET_PREFIX)
            assert decrypt_password(encrypted) == "my-secret-key"

    def test_empty_string_passthrough(self):
        """Empty string returns empty without encryption."""
        assert encrypt_password("") == ""
        assert decrypt_password("") == ""

    def test_plaintext_passthrough(self):
        """Non-Fernet tokens returned as-is (migration path)."""
        assert decrypt_password("plain-api-key-123") == "plain-api-key-123"

    def test_different_key_graceful(self, tmp_path):
        """Decrypting with wrong key returns stored value (doesn't crash)."""
        key_file = tmp_path / "credentials.key"
        with patch("familiar.core.credential_store._key_file_override", key_file):
            encrypted = encrypt_password("secret")

        # Write a different key
        from cryptography.fernet import Fernet
        key_file.write_bytes(Fernet.generate_key())

        with patch("familiar.core.credential_store._key_file_override", key_file):
            # Should return the encrypted blob as-is, not crash
            result = decrypt_password(encrypted)
            assert result == encrypted  # falls back to stored value

    def test_key_file_created(self, tmp_path):
        """Key file auto-generated on first use."""
        key_file = tmp_path / "subdir" / "credentials.key"
        assert not key_file.exists()
        with patch("familiar.core.credential_store._key_file_override", key_file):
            encrypt_password("test")
        assert key_file.exists()

    def test_key_file_reused(self, tmp_path):
        """Same key file used across calls (deterministic)."""
        key_file = tmp_path / "credentials.key"
        with patch("familiar.core.credential_store._key_file_override", key_file):
            enc1 = encrypt_password("test")
        key_bytes = key_file.read_bytes()
        with patch("familiar.core.credential_store._key_file_override", key_file):
            enc2 = encrypt_password("test")
        assert key_file.read_bytes() == key_bytes  # same key
        # Both should decrypt correctly
        with patch("familiar.core.credential_store._key_file_override", key_file):
            assert decrypt_password(enc1) == "test"
            assert decrypt_password(enc2) == "test"


# ── Unified Keyring ──────────────────────────────────────────────────────


class TestKeyring:
    @pytest.fixture(autouse=True)
    def _keyring_tmpdir(self, tmp_path, monkeypatch):
        """Redirect keyring + key file to tmp for every test."""
        keyring_file = tmp_path / "keyring.json"
        key_file = tmp_path / "credentials.key"
        monkeypatch.setattr("familiar.core.credential_store._KEYRING_FILE", keyring_file)
        monkeypatch.setattr("familiar.core.credential_store._key_file_override", key_file)

    def test_set_and_get(self):
        keyring_set("llm.anthropic", "api_key", "sk-ant-12345")
        assert keyring_get("llm.anthropic", "api_key") == "sk-ant-12345"

    def test_get_missing_returns_none(self):
        assert keyring_get("nonexistent", "key") is None

    def test_delete_key(self):
        keyring_set("service.gitea", "password", "hunter2")
        assert keyring_delete("service.gitea", "password") is True
        assert keyring_get("service.gitea", "password") is None

    def test_delete_namespace(self):
        keyring_set("google.calendar", "token", "abc")
        keyring_set("google.calendar", "refresh", "def")
        assert keyring_delete("google.calendar") is True
        assert keyring_get("google.calendar", "token") is None
        assert keyring_get("google.calendar", "refresh") is None

    def test_delete_nonexistent_returns_false(self):
        assert keyring_delete("nope", "nada") is False
        assert keyring_delete("nope") is False

    def test_list_all_namespaces(self):
        keyring_set("ns1", "a", "val")
        keyring_set("ns1", "b", "val")
        keyring_set("ns2", "c", "val")
        listing = keyring_list()
        assert listing == {"ns1": 2, "ns2": 1}

    def test_list_namespace_keys(self):
        keyring_set("dashboard", "api_key", "secret")
        keyring_set("dashboard", "session", "token")
        listing = keyring_list("dashboard")
        assert listing == {"api_key": "***", "session": "***"}

    def test_list_empty_namespace(self):
        assert keyring_list("empty") == {}

    def test_values_encrypted_on_disk(self, tmp_path):
        keyring_set("test", "secret", "plaintext-value")
        raw = json.loads((tmp_path / "keyring.json").read_text())
        stored = raw["test"]["secret"]
        assert stored != "plaintext-value"  # should be encrypted
        assert stored.startswith(_FERNET_PREFIX)

    def test_migrate_env(self, monkeypatch):
        monkeypatch.setenv("MY_API_KEY", "key-from-env")
        assert keyring_migrate_env("llm.openai", "MY_API_KEY") is True
        assert keyring_get("llm.openai", "api_key") == "key-from-env"

    def test_migrate_env_no_overwrite(self, monkeypatch):
        keyring_set("llm.openai", "api_key", "existing")
        monkeypatch.setenv("MY_API_KEY", "new-value")
        assert keyring_migrate_env("llm.openai", "MY_API_KEY") is False
        assert keyring_get("llm.openai", "api_key") == "existing"

    def test_migrate_env_missing_var(self):
        assert keyring_migrate_env("llm.openai", "NONEXISTENT_VAR") is False
