"""Unit tests for ``mgf.common.idempotency`` — C-3 federation primitive (v0.37.0)."""

from __future__ import annotations

import time

import pytest

from mgf.common.idempotency import (
    IdempotencyKeyConflictError,
    IdempotencyRecord,
    IdempotencyStore,
    InMemoryIdempotencyStore,
)


class TestProtocolConformance:
    def test_default_satisfies_protocol(self) -> None:
        assert isinstance(InMemoryIdempotencyStore(), IdempotencyStore)


class TestInMemoryStoreBasic:
    def test_miss_returns_none(self) -> None:
        store = InMemoryIdempotencyStore()
        result = store.get("missing-key", expected_body_hash="abc")
        assert result is None

    def test_put_then_get_returns_record(self) -> None:
        store = InMemoryIdempotencyStore()
        store.put(
            "key-1",
            body_hash="hash1",
            status_code=200,
            body={"ok": True},
        )
        record = store.get("key-1", expected_body_hash="hash1")
        assert record is not None
        assert record.key == "key-1"
        assert record.body_hash == "hash1"
        assert record.status_code == 200
        assert record.body == {"ok": True}

    def test_put_overwrites_same_key(self) -> None:
        store = InMemoryIdempotencyStore()
        store.put("k", body_hash="h1", status_code=200, body={"a": 1})
        store.put("k", body_hash="h1", status_code=200, body={"a": 2})
        record = store.get("k", expected_body_hash="h1")
        assert record.body == {"a": 2}


class TestKeyConflict:
    def test_different_body_hash_raises(self) -> None:
        store = InMemoryIdempotencyStore()
        store.put("k", body_hash="original", status_code=200, body={})
        with pytest.raises(IdempotencyKeyConflictError) as exc_info:
            store.get("k", expected_body_hash="different")
        assert exc_info.value.key == "k"
        assert exc_info.value.expected_body_hash == "original"
        assert exc_info.value.actual_body_hash == "different"

    def test_conflict_message_has_truncated_hashes(self) -> None:
        err = IdempotencyKeyConflictError(
            key="k",
            expected_body_hash="a" * 64,
            actual_body_hash="b" * 64,
        )
        assert "aaaaaaaaaaaa" in str(err)
        assert "bbbbbbbbbbbb" in str(err)


class TestTTL:
    def test_expired_record_returns_none_and_evicts(self) -> None:
        # Use a short TTL.
        store = InMemoryIdempotencyStore(ttl_seconds=0.05)
        store.put("k", body_hash="h", status_code=200, body={})
        assert len(store) == 1
        time.sleep(0.1)
        result = store.get("k", expected_body_hash="h")
        assert result is None
        # Eviction on access.
        assert len(store) == 0

    def test_invalid_ttl_rejected(self) -> None:
        with pytest.raises(ValueError, match="ttl_seconds must be > 0"):
            InMemoryIdempotencyStore(ttl_seconds=0)


class TestStoreLifecycle:
    def test_len_and_clear(self) -> None:
        store = InMemoryIdempotencyStore()
        for i in range(3):
            store.put(f"k{i}", body_hash="h", status_code=200, body={})
        assert len(store) == 3
        store.clear()
        assert len(store) == 0


class TestIdempotencyRecord:
    def test_frozen(self) -> None:
        from dataclasses import FrozenInstanceError

        r = IdempotencyRecord(
            key="k",
            body_hash="h",
            status_code=200,
            body={},
            headers={},
            created_at=1.0,
        )
        with pytest.raises(FrozenInstanceError):
            r.status_code = 500  # type: ignore[misc]
