"""Tests for hash.py: SHA-256 utilities and shallow_hash."""

from dataclasses import dataclass

import pytest
from hypothesis import given
from hypothesis import strategies as st

from ads_foundation.hash import (
    EMPTY_HASH,
    combine_hashes,
    hash_to_hex,
    hex_to_hash,
    sha256,
    shallow_hash,
)


class TestSha256:
    def test_returns_32_bytes(self):
        assert len(sha256(b"hello")) == 32

    def test_deterministic(self):
        assert sha256(b"hello") == sha256(b"hello")

    def test_different_inputs_differ(self):
        assert sha256(b"hello") != sha256(b"world")


class TestCombineHashes:
    def test_deterministic(self):
        a, b = sha256(b"a"), sha256(b"b")
        assert combine_hashes(a, b) == combine_hashes(a, b)

    def test_order_matters(self):
        a, b = sha256(b"a"), sha256(b"b")
        assert combine_hashes(a, b) != combine_hashes(b, a)

    def test_returns_32_bytes(self):
        assert len(combine_hashes(sha256(b"a"), sha256(b"b"))) == 32


class TestEmptyHash:
    def test_is_32_bytes(self):
        assert len(EMPTY_HASH) == 32

    def test_is_consistent(self):
        assert EMPTY_HASH == sha256(b"")


class TestHexConversion:
    def test_roundtrip(self):
        h = sha256(b"test")
        assert hex_to_hash(hash_to_hex(h)) == h


class TestTypeDiscrimination:
    """Critical Python-specific tests: 1 == True in Python, but hashes must differ."""

    def test_int_vs_bool_true(self):
        assert shallow_hash(1) != shallow_hash(True)

    def test_int_vs_bool_false(self):
        assert shallow_hash(0) != shallow_hash(False)

    def test_int_vs_str(self):
        assert shallow_hash(1) != shallow_hash("1")

    def test_int_vs_float(self):
        assert shallow_hash(1) != shallow_hash(1.0)

    def test_zero_vs_empty_string(self):
        assert shallow_hash(0) != shallow_hash("")

    def test_zero_vs_none(self):
        assert shallow_hash(0) != shallow_hash(None)

    def test_false_vs_none(self):
        assert shallow_hash(False) != shallow_hash(None)

    def test_empty_string_vs_none(self):
        assert shallow_hash("") != shallow_hash(None)

    def test_empty_bytes_vs_none(self):
        assert shallow_hash(b"") != shallow_hash(None)


class TestShallowHashBuiltins:
    def test_int(self):
        h = shallow_hash(42)
        assert len(h) == 32

    def test_str(self):
        h = shallow_hash("hello")
        assert len(h) == 32

    def test_float(self):
        h = shallow_hash(3.14)
        assert len(h) == 32

    def test_bool(self):
        assert len(shallow_hash(True)) == 32
        assert len(shallow_hash(False)) == 32

    def test_bytes(self):
        assert len(shallow_hash(b"data")) == 32

    def test_none(self):
        assert len(shallow_hash(None)) == 32

    def test_tuple(self):
        h = shallow_hash((1, 2, 3))
        assert len(h) == 32

    def test_list(self):
        h = shallow_hash([1, 2, 3])
        assert len(h) == 32

    def test_empty_list(self):
        h = shallow_hash([])
        assert len(h) == 32

    def test_list_vs_tuple(self):
        assert shallow_hash([1, 2]) != shallow_hash((1, 2))


class TestShallowHashCustomType:
    def test_ads_hash_protocol(self):
        @dataclass
        class Point:
            x: int
            y: int

            def __ads_hash__(self) -> bytes:
                return sha256(f"Point:{self.x},{self.y}".encode())

        p = Point(1, 2)
        h = shallow_hash(p)
        assert len(h) == 32
        assert h == sha256(b"Point:1,2")

    def test_unsupported_type_raises(self):
        with pytest.raises(TypeError, match="Cannot shallow-hash"):
            shallow_hash(object())


class TestShallowHashHypothesis:
    @given(st.integers())
    def test_int_determinism(self, value):
        assert shallow_hash(value) == shallow_hash(value)

    @given(st.text())
    def test_str_determinism(self, value):
        assert shallow_hash(value) == shallow_hash(value)

    @given(st.floats(allow_nan=False))
    def test_float_determinism(self, value):
        assert shallow_hash(value) == shallow_hash(value)

    @given(st.binary())
    def test_bytes_determinism(self, value):
        assert shallow_hash(value) == shallow_hash(value)

    @given(st.booleans())
    def test_bool_determinism(self, value):
        assert shallow_hash(value) == shallow_hash(value)

    @given(st.integers())
    def test_hash_is_32_bytes(self, value):
        assert len(shallow_hash(value)) == 32
