import hmac
import pickle
from hashlib import sha256
from pathlib import Path

import pytest
from django.test import override_settings

from paperless.celery import signed_pickle_dumps
from paperless.celery import signed_pickle_loads
from paperless.signed_pickle import HMAC_SIZE


class TestSignedPickleSerializer:
    def test_roundtrip_simple_types(self) -> None:
        """Signed pickle can round-trip basic JSON-like types."""
        for obj in [42, "hello", [1, 2, 3], {"key": "value"}, None, True]:
            assert signed_pickle_loads(signed_pickle_dumps(obj)) == obj

    def test_roundtrip_complex_types(self) -> None:
        """Signed pickle can round-trip types that JSON cannot."""

        obj = {"path": Path("/tmp/test"), "data": {1, 2, 3}}
        result = signed_pickle_loads(signed_pickle_dumps(obj))
        assert result["path"] == Path("/tmp/test")
        assert result["data"] == {1, 2, 3}

    def test_tampered_data_rejected(self) -> None:
        """Flipping a byte in the data portion causes HMAC failure."""
        payload = signed_pickle_dumps({"task": "test"})
        tampered = bytearray(payload)
        tampered[-1] ^= 0xFF
        with pytest.raises(ValueError, match="HMAC verification failed"):
            signed_pickle_loads(bytes(tampered))

    def test_tampered_signature_rejected(self) -> None:
        """Flipping a byte in the signature portion causes HMAC failure."""
        payload = signed_pickle_dumps({"task": "test"})
        tampered = bytearray(payload)
        tampered[0] ^= 0xFF
        with pytest.raises(ValueError, match="HMAC verification failed"):
            signed_pickle_loads(bytes(tampered))

    def test_truncated_payload_rejected(self) -> None:
        """A payload shorter than HMAC_SIZE is rejected."""
        with pytest.raises(ValueError, match="too short"):
            signed_pickle_loads(b"\x00" * (HMAC_SIZE - 1))

    def test_empty_payload_rejected(self) -> None:
        with pytest.raises(ValueError, match="too short"):
            signed_pickle_loads(b"")

    @override_settings(SECRET_KEY="different-secret-key")
    def test_wrong_secret_key_rejected(self) -> None:
        """A message signed with one key cannot be loaded with another."""
        original_key = b"test-secret-key-do-not-use-in-production"
        obj = {"task": "test"}
        data = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL)
        signature = hmac.new(original_key, data, sha256).digest()
        payload = signature + data
        with pytest.raises(ValueError, match="HMAC verification failed"):
            signed_pickle_loads(payload)

    def test_forged_pickle_rejected(self) -> None:
        """A raw pickle payload (no signature) is rejected."""
        raw_pickle = pickle.dumps({"task": "test"})
        # Raw pickle won't have a valid HMAC prefix
        with pytest.raises(ValueError, match="HMAC verification failed"):
            signed_pickle_loads(b"\x00" * HMAC_SIZE + raw_pickle)
