"""Unit tests for the encrypted column types and their optional-dependency facade."""

from typing import Union

import pytest
from sqlalchemy import column, literal

from advanced_alchemy.types.encrypted_string import EncryptedString, EncryptedText


def test_cryptography_installed_flag_is_bool() -> None:
    from advanced_alchemy.typing import CRYPTOGRAPHY_INSTALLED

    assert isinstance(CRYPTOGRAPHY_INSTALLED, bool)


def test_fernet_backend_requires_cryptography(monkeypatch: pytest.MonkeyPatch) -> None:
    from advanced_alchemy.exceptions import MissingDependencyError
    from advanced_alchemy.types import encrypted_string

    monkeypatch.setattr(encrypted_string, "CRYPTOGRAPHY_INSTALLED", False)
    with pytest.raises(MissingDependencyError):
        encrypted_string.FernetBackend()


def test_encrypted_string_default_key_warns() -> None:
    with pytest.warns(DeprecationWarning, match="key"):
        EncryptedString()


def test_encrypted_text_default_key_warns() -> None:
    with pytest.warns(DeprecationWarning, match="key"):
        EncryptedText()


def test_encrypted_string_explicit_key_no_warning(recwarn: pytest.WarningsRecorder) -> None:
    EncryptedString(key="explicit-key")
    assert not [w for w in recwarn.list if issubclass(w.category, DeprecationWarning)]


def test_subclass_repr_renders_own_name() -> None:
    """EncryptedText must repr as EncryptedText so Alembic reconstructs the right type.

    A previously shared __repr__ hardcoded ``EncryptedString``, so an EncryptedText column
    would be regenerated as the wrong (VARCHAR-backed) type in autogenerated migrations.
    """
    assert repr(EncryptedText(key="k")) == "EncryptedText(key='k', backend=FernetBackend, length=None)"
    assert repr(EncryptedString(key="k")) == "EncryptedString(key='k', backend=FernetBackend, length=None)"


def test_fernet_backend_is_client_side() -> None:
    """Fernet encrypts in Python, so it advertises no SQL-side expression."""
    from advanced_alchemy.types.encrypted_string import FernetBackend

    backend = FernetBackend()
    backend.mount_vault("k")
    assert backend.bind_expression("v") is None
    assert backend.column_expression("c") is None


def test_pgcrypto_backend_wraps_sql() -> None:
    """PGCrypto runs server-side, wrapping binds/reads in pgcrypto functions."""
    from advanced_alchemy.types.encrypted_string import PGCryptoBackend

    backend = PGCryptoBackend()
    backend.mount_vault("k")
    bind_expr = backend.bind_expression(literal("secret"))
    column_expr = backend.column_expression(column("c"))
    assert bind_expr is not None
    assert column_expr is not None
    bind_sql = str(bind_expr)
    column_sql = str(column_expr)
    assert "pgp_sym_encrypt" in bind_sql
    assert "armor" in bind_sql
    assert "pgp_sym_decrypt" in column_sql
    assert "dearmor" in column_sql


def test_pgcrypto_different_keys_distinct_cache_key() -> None:
    """Columns with different keys must not collide in the statement cache."""
    from advanced_alchemy.types.encrypted_string import PGCryptoBackend

    a = EncryptedString(key="key-a", backend=PGCryptoBackend)
    b = EncryptedString(key="key-b", backend=PGCryptoBackend)
    assert a._static_cache_key != b._static_cache_key


def test_fernet_ciphertext_backward_compat() -> None:
    """A token written by an earlier release must still decrypt with the same key.

    Locks the SHA256 key derivation and Fernet format so future changes cannot
    silently render existing rows undecryptable.
    """
    golden_token = "gAAAAABqHELupN0FK6NTQxUQD-7kN1TOxrgP9pVU_NaaW31Y7wljqqG16DHn9ZXxNb2Jqt2UtMqr-Bcjegh84-E6VHkDq1F-Ic8TXqTn2q_i4u-vv_0qEl8="
    enc = EncryptedString(key="golden-key")
    enc.mount_vault()
    assert enc.backend.decrypt(golden_token) == "golden-plaintext"


def test_static_key_caches_vault(monkeypatch: pytest.MonkeyPatch) -> None:
    enc = EncryptedString(key="static-key")
    calls = {"n": 0}
    original = enc.backend.mount_vault

    def counting(key: "Union[str, bytes]") -> None:
        calls["n"] += 1
        original(key)

    monkeypatch.setattr(enc.backend, "mount_vault", counting)
    enc.mount_vault()
    enc.mount_vault()
    assert calls["n"] == 1


def test_callable_key_remounts(monkeypatch: pytest.MonkeyPatch) -> None:
    enc = EncryptedString(key=lambda: "rotating-key")
    calls = {"n": 0}
    original = enc.backend.mount_vault

    def counting(key: "Union[str, bytes]") -> None:
        calls["n"] += 1
        original(key)

    monkeypatch.setattr(enc.backend, "mount_vault", counting)
    enc.mount_vault()
    enc.mount_vault()
    assert calls["n"] == 2
