"""Tests for core.py: Auth, ProofStream, auth/unauth, context managers."""

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

from ads_foundation.core import (
    Auth,
    Mode,
    ProofStream,
    auth,
    prover_context,
    run_prover,
    run_verifier,
    unauth,
    verifier_context,
)
from ads_foundation.errors import ADSError, ProofError, VerificationError
from ads_foundation.hash import sha256


class TestAuth:
    def test_prover_has_value(self):
        a = Auth.prover(b"\x00" * 32, 42)
        assert a.is_prover
        assert a.unwrap() == 42
        assert a.digest() == b"\x00" * 32

    def test_verifier_has_no_value(self):
        a = Auth.verifier(b"\x00" * 32)
        assert not a.is_prover
        assert a.digest() == b"\x00" * 32

    def test_unwrap_raises_for_verifier(self):
        a = Auth.verifier(b"\x00" * 32)
        with pytest.raises(ADSError, match="Cannot unwrap"):
            a.unwrap()

    def test_frozen(self):
        a = Auth.prover(b"\x00" * 32, 42)
        with pytest.raises(AttributeError):
            a._hash = b"\x01" * 32  # type: ignore[misc]


class TestProofStream:
    def test_prover_append(self):
        ps = ProofStream(Mode.PROVER)
        h = sha256(b"test")
        ps.append(h)
        assert ps.get_proof() == [h]

    def test_verifier_consume(self):
        h = sha256(b"test")
        ps = ProofStream(Mode.VERIFIER, [h])
        assert ps.consume() == h
        assert ps.is_complete

    def test_prover_cannot_consume(self):
        ps = ProofStream(Mode.PROVER)
        with pytest.raises(ADSError, match="Only verifier"):
            ps.consume()

    def test_verifier_cannot_append(self):
        ps = ProofStream(Mode.VERIFIER, [])
        with pytest.raises(ADSError, match="Only prover"):
            ps.append(sha256(b"test"))

    def test_consume_exhausted_raises(self):
        ps = ProofStream(Mode.VERIFIER, [])
        with pytest.raises(ProofError, match="exhausted"):
            ps.consume()

    def test_append_after_finalize_raises(self):
        ps = ProofStream(Mode.PROVER)
        ps._finalize()
        with pytest.raises(ADSError, match="finalized"):
            ps.append(sha256(b"test"))

    def test_consume_after_finalize_raises(self):
        h = sha256(b"test")
        ps = ProofStream(Mode.VERIFIER, [h])
        ps._finalize()
        with pytest.raises(ADSError, match="finalized"):
            ps.consume()

    def test_len(self):
        ps = ProofStream(Mode.PROVER)
        assert len(ps) == 0
        ps.append(sha256(b"a"))
        assert len(ps) == 1

    @given(st.lists(st.binary(min_size=32, max_size=32), min_size=1, max_size=20))
    def test_append_consume_roundtrip(self, hashes):
        """Append N hashes in prover, consume N in verifier -- all match in order."""
        ps_p = ProofStream(Mode.PROVER)
        for h in hashes:
            ps_p.append(h)
        proof = ps_p.get_proof()

        ps_v = ProofStream(Mode.VERIFIER, proof)
        consumed = [ps_v.consume() for _ in hashes]
        assert consumed == hashes
        assert ps_v.is_complete


class TestAuthUnauth:
    def test_auth_prover_mode(self):
        ctx = ProofStream(Mode.PROVER)
        a = auth(42, ctx)
        assert a.is_prover
        assert a.unwrap() == 42

    def test_auth_verifier_mode(self):
        ctx = ProofStream(Mode.VERIFIER)
        a = auth(42, ctx)
        assert not a.is_prover

    def test_unauth_prover_appends(self):
        ctx = ProofStream(Mode.PROVER)
        a = auth(42, ctx)
        val = unauth(a, ctx)
        assert val == 42
        assert len(ctx.get_proof()) == 1

    def test_unauth_verifier_matching_hash(self):
        # Build proof in prover mode
        p_ctx = ProofStream(Mode.PROVER)
        a = auth(42, p_ctx)
        unauth(a, p_ctx)
        proof = p_ctx.get_proof()

        # Verify in verifier mode
        v_ctx = ProofStream(Mode.VERIFIER, proof)
        a_v = auth(42, v_ctx)
        result = unauth(a_v, v_ctx)
        assert result is None  # verifier gets None
        assert v_ctx.is_complete

    def test_unauth_verifier_mismatch_raises(self):
        v_ctx = ProofStream(Mode.VERIFIER, [b"\xff" * 32])
        a_v = Auth.verifier(b"\x00" * 32)
        with pytest.raises(VerificationError, match="Hash mismatch"):
            unauth(a_v, v_ctx)


class TestContextManagers:
    def test_prover_context(self):
        with prover_context() as ctx:
            a = auth(42, ctx)
            unauth(a, ctx)
        proof = ctx.get_proof()
        assert len(proof) == 1

    def test_verifier_context_success(self):
        with prover_context() as p:
            a = auth(42, p)
            unauth(a, p)
        proof = p.get_proof()

        with verifier_context(proof) as v:
            a_v = auth(42, v)
            unauth(a_v, v)

    def test_verifier_context_incomplete_raises(self):
        with pytest.raises(VerificationError, match="unused proof elements"):
            with verifier_context([b"\x00" * 32, b"\x00" * 32]) as ctx:
                ctx.consume()

    def test_prover_finalized_after_exit(self):
        with prover_context() as ctx:
            pass
        with pytest.raises(ADSError, match="finalized"):
            ctx.append(sha256(b"test"))


class TestFunctionalAPI:
    def test_run_prover(self):
        def op(x, ctx):
            a = auth(x, ctx)
            return unauth(a, ctx)

        result, proof = run_prover(op, 42)
        assert result == 42
        assert len(proof) == 1

    def test_run_verifier(self):
        def op(x, ctx):
            a = auth(x, ctx)
            return unauth(a, ctx)

        _, proof = run_prover(op, 42)

        result = run_verifier(proof, op, 42)
        assert result is None
