"""
Unit tests for encryption services.

Tests CryptoService (Fernet session encryption) and
report_crypto (AES-256-GCM report encryption).
"""

import os
import sys
import pytest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../.."))


# ============================================================================
# CryptoService (backend session encryption)
# ============================================================================


class TestCryptoService:
    def test_encrypt_decrypt_roundtrip(self):
        from app.modules.terminal.auto.crypto import CryptoService
        crypto = CryptoService(master_key="test-secret-key-for-testing-1234")
        plaintext = "my-secret-api-key-12345"
        encrypted = crypto.encrypt(plaintext)
        assert encrypted != plaintext
        decrypted = crypto.decrypt(encrypted)
        assert decrypted == plaintext

    def test_different_plaintexts_different_ciphertexts(self):
        from app.modules.terminal.auto.crypto import CryptoService
        crypto = CryptoService(master_key="test-secret-key-for-testing-1234")
        e1 = crypto.encrypt("alpha")
        e2 = crypto.encrypt("bravo")
        assert e1 != e2

    def test_encrypt_empty_string(self):
        from app.modules.terminal.auto.crypto import CryptoService
        crypto = CryptoService(master_key="test-secret-key-for-testing-1234")
        encrypted = crypto.encrypt("")
        assert crypto.decrypt(encrypted) == ""

    def test_encrypt_long_rsa_key(self):
        from app.modules.terminal.auto.crypto import CryptoService
        from tests.conftest import TEST_RSA_KEY
        crypto = CryptoService(master_key="test-secret-key-for-testing-1234")
        encrypted = crypto.encrypt(TEST_RSA_KEY)
        assert crypto.decrypt(encrypted) == TEST_RSA_KEY

    def test_legacy_private_key_roundtrip(self):
        from app.modules.terminal.auto.crypto import CryptoService
        crypto = CryptoService(master_key="test-secret-key-for-testing-1234")
        secret = "super-secret-private-key-content"
        encrypted = crypto.encrypt_private_key(secret)
        assert encrypted != secret
        decrypted = crypto.decrypt_private_key(encrypted)
        assert decrypted == secret

    def test_missing_master_key_raises(self):
        from app.modules.terminal.auto.crypto import CryptoService
        # Remove SECRET_KEY from env temporarily
        old = os.environ.pop("SECRET_KEY", None)
        try:
            with pytest.raises(ValueError, match="SECRET_KEY"):
                CryptoService(master_key=None)
        finally:
            if old:
                os.environ["SECRET_KEY"] = old

    def test_wrong_key_cannot_decrypt(self):
        from app.modules.terminal.auto.crypto import CryptoService
        crypto1 = CryptoService(master_key="key-one-for-testing-12345678")
        crypto2 = CryptoService(master_key="key-two-for-testing-12345678")
        encrypted = crypto1.encrypt("secret")
        with pytest.raises(Exception):  # InvalidToken
            crypto2.decrypt(encrypted)


# ============================================================================
# report_crypto (AES-256-GCM)
# ============================================================================

try:
    import agentic  # noqa: F401
    _has_agentic = True
except ImportError:
    _has_agentic = False

_skip_no_agentic = pytest.mark.skipif(not _has_agentic, reason="agentic module not available")


@_skip_no_agentic
class TestReportCrypto:
    def test_encrypt_decrypt_roundtrip(self):
        from agentic.syndicate.report_crypto import encrypt_report, decrypt_report
        plaintext = '{"instance_id": 1, "markets": ["KXBTC"]}'
        enc = encrypt_report(plaintext)
        assert enc.ciphertext != plaintext.encode()
        assert len(enc.nonce) == 12
        assert len(enc.key) == 32
        decrypted = decrypt_report(enc.ciphertext, enc.nonce, enc.key)
        assert decrypted == plaintext

    def test_different_keys_per_encryption(self):
        from agentic.syndicate.report_crypto import encrypt_report
        e1 = encrypt_report("same text")
        e2 = encrypt_report("same text")
        assert e1.key != e2.key
        assert e1.nonce != e2.nonce

    def test_wrong_key_fails(self):
        from agentic.syndicate.report_crypto import encrypt_report, decrypt_report
        import secrets
        enc = encrypt_report("secret data")
        wrong_key = secrets.token_bytes(32)
        with pytest.raises(Exception):  # InvalidTag
            decrypt_report(enc.ciphertext, enc.nonce, wrong_key)

    def test_wrong_nonce_fails(self):
        from agentic.syndicate.report_crypto import encrypt_report, decrypt_report
        import secrets
        enc = encrypt_report("secret data")
        wrong_nonce = secrets.token_bytes(12)
        with pytest.raises(Exception):
            decrypt_report(enc.ciphertext, wrong_nonce, enc.key)

    def test_chain_bytes_serialization(self):
        from agentic.syndicate.report_crypto import encrypt_report, EncryptedReport, REPORT_VERSION
        enc = encrypt_report("test report content")
        chain_bytes = enc.to_chain_bytes()

        # First byte = version
        assert chain_bytes[0] == REPORT_VERSION
        # Bytes 1-13 = nonce
        assert chain_bytes[1:13] == enc.nonce
        # Rest = ciphertext
        assert chain_bytes[13:] == enc.ciphertext

    def test_parse_chain_bytes(self):
        from agentic.syndicate.report_crypto import encrypt_report, EncryptedReport
        enc = encrypt_report("roundtrip test")
        chain_bytes = enc.to_chain_bytes()

        version, nonce, ciphertext = EncryptedReport.parse_chain_bytes(chain_bytes)
        assert version == 1
        assert nonce == enc.nonce
        assert ciphertext == enc.ciphertext

    def test_encrypt_large_report(self):
        from agentic.syndicate.report_crypto import encrypt_report, decrypt_report
        large_text = "x" * 100_000
        enc = encrypt_report(large_text)
        assert decrypt_report(enc.ciphertext, enc.nonce, enc.key) == large_text

    def test_encrypt_unicode(self):
        from agentic.syndicate.report_crypto import encrypt_report, decrypt_report
        text = "Syndicate eternal 🎰 Spreads temporary 📊"
        enc = encrypt_report(text)
        assert decrypt_report(enc.ciphertext, enc.nonce, enc.key) == text


# ============================================================================
# ReportKeyManager
# ============================================================================


@_skip_no_agentic
class TestReportKeyManager:
    def test_store_and_retrieve(self, fake_redis):
        from agentic.syndicate.report_crypto import ReportKeyManager
        mgr = ReportKeyManager(fake_redis)
        mgr.store_report_key(
            tx_hash="0xabc123",
            aes_key=b"\x01" * 32,
            author_address="0xAuthor",
            instance_id=42,
            markets=["KXBTC-25MAR05-YES"],
            timestamp="1700000000",
        )
        # Author can always access
        key_hex = mgr.grant_access("0xabc123", "0xAuthor", "0xAuthor")
        assert key_hex == ("01" * 32)

    def test_list_reports_excludes_key(self, fake_redis):
        from agentic.syndicate.report_crypto import ReportKeyManager
        mgr = ReportKeyManager(fake_redis)
        mgr.store_report_key(
            tx_hash="0xdef456",
            aes_key=b"\x02" * 32,
            author_address="0xBob",
            instance_id=1,
            markets=["M1"],
            timestamp="1700000001",
        )
        reports = mgr.list_reports_by_author("0xBob")
        assert len(reports) == 1
        assert "aes_key_hex" not in reports[0]
        assert reports[0]["tx_hash"] == "0xdef456"

    def test_non_pledged_denied(self, fake_redis):
        from agentic.syndicate.report_crypto import ReportKeyManager
        mgr = ReportKeyManager(fake_redis)
        mgr.store_report_key(
            tx_hash="0xaaa",
            aes_key=b"\x03" * 32,
            author_address="0xAlice",
            instance_id=1,
            markets=["M1"],
            timestamp="1700000002",
        )
        # Non-author, non-pledged member
        result = mgr.grant_access("0xaaa", "0xStranger", "0xAlice")
        assert result is None

    def test_missing_report_returns_none(self, fake_redis):
        from agentic.syndicate.report_crypto import ReportKeyManager
        mgr = ReportKeyManager(fake_redis)
        result = mgr.grant_access("0xnonexistent", "0xAnyone", "0xAnyone")
        assert result is None
