import sqlite3
from concurrent.futures import ThreadPoolExecutor
from threading import Barrier

from src.idempotency import IdempotencyLedger


def test_completed_result_replays_after_ledger_reconstruction(tmp_path):
    database = tmp_path / "idempotency.sqlite3"
    first = IdempotencyLedger(database, ttl_seconds=600, max_entries=256)

    reservation = first.reserve("create-201", ("issue-create", "stackchain/api", "Ship it"))
    assert reservation.state == "reserved"
    first.complete("create-201", {"number": 42, "title": "Ship it"})

    reconstructed = IdempotencyLedger(database, ttl_seconds=600, max_entries=256)
    replay = reconstructed.reserve(
        "create-201", ("issue-create", "stackchain/api", "Ship it")
    )

    assert replay.state == "completed"
    assert replay.response == {"number": 42, "title": "Ship it"}
    assert sqlite3.connect(database).execute(
        "SELECT status FROM idempotency_operations WHERE key = ?", ("create-201",)
    ).fetchone() == ("completed",)


def test_reservation_expires_completed_rows_and_enforces_capacity(tmp_path):
    now = [1_000.0]
    ledger = IdempotencyLedger(
        tmp_path / "bounded.sqlite3",
        ttl_seconds=10,
        max_entries=2,
        clock=lambda: now[0],
    )
    assert ledger.reserve("old", ("comment", "old")).state == "reserved"
    ledger.complete("old", {"id": 1})
    now[0] += 11

    assert ledger.reserve("new-1", ("comment", "new-1")).state == "reserved"
    ledger.complete("new-1", {"id": 2})
    assert ledger.reserve("new-2", ("comment", "new-2")).state == "reserved"
    ledger.complete("new-2", {"id": 3})
    replacement = ledger.reserve("new-3", ("comment", "new-3"))

    assert replacement.state == "reserved"
    with sqlite3.connect(ledger.path) as connection:
        keys = connection.execute(
            "SELECT key FROM idempotency_operations ORDER BY key"
        ).fetchall()
        indexes = connection.execute(
            "PRAGMA index_list(idempotency_operations)"
        ).fetchall()
    assert keys == [("new-2",), ("new-3",)]
    assert any(index[1] == "idempotency_completed_at_idx" for index in indexes)


def test_independent_connections_atomically_reserve_one_operation(tmp_path):
    database = tmp_path / "workers.sqlite3"
    ledgers = [
        IdempotencyLedger(database, ttl_seconds=600, max_entries=256)
        for _ in range(2)
    ]
    barrier = Barrier(2)

    def reserve(ledger):
        barrier.wait()
        return ledger.reserve("worker-key-201", ("review", "stackchain/api", 7)).state

    with ThreadPoolExecutor(max_workers=2) as executor:
        states = list(executor.map(reserve, ledgers))

    assert sorted(states) == ["pending", "reserved"]


def test_stale_pending_reservation_becomes_uncertain_without_consuming_capacity(tmp_path):
    now = [1_000.0]
    ledger = IdempotencyLedger(
        tmp_path / "orphaned.sqlite3",
        ttl_seconds=10,
        max_entries=1,
        clock=lambda: now[0],
    )
    fingerprint = ("issue-comment", "stackchain/api", 7, "Verify first")

    assert ledger.reserve("orphan", fingerprint).state == "reserved"
    now[0] += 11

    assert ledger.reserve("orphan", fingerprint).state == "uncertain"
    assert ledger.reserve("unrelated", ("issue-comment", "stackchain/api", 8, "Continue")).state == "reserved"
    assert ledger.reserve("orphan", ("issue-comment", "stackchain/api", 7, "Changed")).state == "conflict"
