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

"""Tests for encryption.py — EncryptedStorage roundtrip and graceful degradation."""

import json
import os

import pytest

from familiar.core.encryption import (
    CRYPTOGRAPHY_AVAILABLE,
    EncryptedStorage,
    EncryptionConfig,
    EncryptionError,
    EncryptionKeyError,
)


@pytest.fixture
def fernet_key():
    """Generate a valid Fernet key for testing."""
    from cryptography.fernet import Fernet
    return Fernet.generate_key().decode()


@pytest.fixture
def storage(fernet_key):
    """EncryptedStorage with a test key."""
    return EncryptedStorage(key=fernet_key)


# ── Roundtrip ────────────────────────────────────────────────────────────


class TestRoundtrip:
    def test_string_roundtrip(self, storage):
        encrypted = storage.encrypt("hello world")
        assert encrypted != "hello world"
        assert storage.decrypt(encrypted) == "hello world"

    def test_bytes_roundtrip(self, storage):
        encrypted = storage.encrypt(b"binary data")
        assert storage.decrypt(encrypted) == "binary data"

    def test_json_roundtrip(self, storage):
        data = {"users": [{"name": "George", "role": "admin"}]}
        encrypted = storage.encrypt(json.dumps(data))
        assert json.loads(storage.decrypt(encrypted)) == data

    def test_empty_string(self, storage):
        encrypted = storage.encrypt("")
        assert storage.decrypt(encrypted) == ""

    def test_unicode_roundtrip(self, storage):
        text = "日本語テスト 🐉"
        encrypted = storage.encrypt(text)
        assert storage.decrypt(encrypted) == text

    def test_large_payload(self, storage):
        data = "x" * 100_000
        encrypted = storage.encrypt(data)
        assert storage.decrypt(encrypted) == data


# ── Key management ───────────────────────────────────────────────────────


class TestKeyManagement:
    def test_direct_key(self, fernet_key):
        s = EncryptedStorage(key=fernet_key)
        assert s.is_available

    def test_env_key(self, fernet_key, monkeypatch):
        monkeypatch.setenv("FAMILIAR_ENCRYPTION_KEY", fernet_key)
        s = EncryptedStorage()
        assert s.is_available
        assert s.decrypt(s.encrypt("test")) == "test"

    def test_no_key_not_available(self, monkeypatch):
        monkeypatch.delenv("FAMILIAR_ENCRYPTION_KEY", raising=False)
        monkeypatch.delenv("FAMILIAR_ENCRYPTION_PASSWORD", raising=False)
        s = EncryptedStorage()
        assert not s.is_available

    def test_invalid_key_raises(self):
        with pytest.raises(EncryptionKeyError):
            EncryptedStorage(key="not-a-valid-fernet-key")

    def test_password_derivation(self, tmp_path, monkeypatch):
        salt_file = str(tmp_path / ".salt")
        monkeypatch.setenv("FAMILIAR_ENCRYPTION_PASSWORD", "my-strong-password")
        config = EncryptionConfig(use_password_derivation=True, salt_file=salt_file)
        s = EncryptedStorage(config=config)
        assert s.is_available
        assert s.decrypt(s.encrypt("secret")) == "secret"

    def test_password_derivation_stable(self, tmp_path, monkeypatch):
        """Same password + salt produces same key."""
        salt_file = str(tmp_path / ".salt")
        monkeypatch.setenv("FAMILIAR_ENCRYPTION_PASSWORD", "password123")
        config = EncryptionConfig(use_password_derivation=True, salt_file=salt_file)
        s1 = EncryptedStorage(config=config)
        encrypted = s1.encrypt("data")
        # New instance, same salt file
        s2 = EncryptedStorage(config=config)
        assert s2.decrypt(encrypted) == "data"


# ── Graceful degradation ─────────────────────────────────────────────────


class TestGracefulDegradation:
    def test_encrypt_without_key_raises(self, monkeypatch):
        monkeypatch.delenv("FAMILIAR_ENCRYPTION_KEY", raising=False)
        s = EncryptedStorage()
        with pytest.raises(EncryptionKeyError):
            s.encrypt("data")

    def test_decrypt_wrong_key_raises(self, fernet_key):
        from cryptography.fernet import Fernet
        s1 = EncryptedStorage(key=fernet_key)
        encrypted = s1.encrypt("secret")
        s2 = EncryptedStorage(key=Fernet.generate_key().decode())
        with pytest.raises(EncryptionError):
            s2.decrypt(encrypted)


# ── File operations ──────────────────────────────────────────────────────


class TestFileOps:
    def test_encrypt_file(self, storage, tmp_path):
        src = tmp_path / "data.json"
        dst = tmp_path / "data.json.enc"
        src.write_text('{"key": "value"}')
        storage.encrypt_file(src, dst)
        assert dst.exists()
        assert dst.read_text() != '{"key": "value"}'

    def test_decrypt_file(self, storage, tmp_path):
        src = tmp_path / "data.json"
        enc = tmp_path / "data.json.enc"
        out = tmp_path / "data.json.dec"
        src.write_text('{"key": "value"}')
        storage.encrypt_file(src, enc)
        storage.decrypt_file(enc, out)
        assert out.read_text() == '{"key": "value"}'
