"""Tests for the review queue (JSONL + fcntl.flock)."""
import json
import threading
import pytest


def test_enqueue_creates_file_and_writes_entry(tmp_path):
    from recoil.pipeline._lib.review_queue import enqueue
    queue_path = tmp_path / "review_queue.jsonl"
    entry = enqueue(
        queue_path=queue_path,
        project="test-proj",
        episode_id="EP001",
        shot_id="EP001_SH003",
        run_id="run_abc123",
        reason="attempts_exhausted",
        failure_mode="anatomy_face_merge",
        attempts=[{"attempt": 1, "output": "/tmp/a.jpg"}],
        total_cost_usd=0.536,
        original_prompt="Alice strides through the warehouse",
    )
    assert queue_path.exists()
    assert entry["rq_id"].startswith("rq_")
    assert entry["status"] == "pending"
    lines = queue_path.read_text().strip().splitlines()
    assert len(lines) == 1
    parsed = json.loads(lines[0])
    assert parsed["shot_id"] == "EP001_SH003"


def test_list_pending_filters_resolved(tmp_path):
    from recoil.pipeline._lib.review_queue import enqueue, list_pending, resolve
    queue_path = tmp_path / "review_queue.jsonl"
    e1 = enqueue(queue_path=queue_path, project="p", episode_id="E1",
                 shot_id="SH01", run_id="r1", reason="attempts_exhausted",
                 failure_mode="unknown", attempts=[], total_cost_usd=0.1)
    e2 = enqueue(queue_path=queue_path, project="p", episode_id="E1",
                 shot_id="SH02", run_id="r1", reason="icu_escalated",
                 failure_mode="unknown", attempts=[], total_cost_usd=0.2)
    resolve(queue_path=queue_path, rq_id=e1["rq_id"], resolution="approved")
    pending = list_pending(queue_path=queue_path)
    assert len(pending) == 1
    assert pending[0]["shot_id"] == "SH02"


def test_resolve_appends_new_line(tmp_path):
    """resolve() APPENDS a new line, does NOT rewrite the file."""
    from recoil.pipeline._lib.review_queue import enqueue, resolve
    queue_path = tmp_path / "review_queue.jsonl"
    entry = enqueue(queue_path=queue_path, project="p", episode_id="E1",
                    shot_id="SH01", run_id="r1", reason="attempts_exhausted",
                    failure_mode="unknown", attempts=[], total_cost_usd=0.1)
    resolve(queue_path=queue_path, rq_id=entry["rq_id"], resolution="rejected")
    # File should have 2 lines: original enqueue + resolve append
    lines = queue_path.read_text().strip().splitlines()
    assert len(lines) == 2
    # First line is the original pending entry
    first = json.loads(lines[0])
    assert first["status"] == "pending"
    # Second line is the resolve entry
    second = json.loads(lines[1])
    assert second["status"] == "resolved"
    assert second["rq_id"] == entry["rq_id"]


def test_resolve_updates_entry_via_list_all(tmp_path):
    from recoil.pipeline._lib.review_queue import enqueue, resolve, list_all
    queue_path = tmp_path / "review_queue.jsonl"
    entry = enqueue(queue_path=queue_path, project="p", episode_id="E1",
                    shot_id="SH01", run_id="r1", reason="attempts_exhausted",
                    failure_mode="unknown", attempts=[], total_cost_usd=0.1)
    resolve(queue_path=queue_path, rq_id=entry["rq_id"], resolution="rejected")
    all_entries = list_all(queue_path=queue_path)
    resolved = [e for e in all_entries if e["rq_id"] == entry["rq_id"]]
    assert len(resolved) == 1
    assert resolved[0]["status"] == "resolved"
    assert resolved[0]["resolution"] == "rejected"
    assert resolved[0]["resolved_at"] is not None


def test_concurrent_enqueue_writes(tmp_path):
    """Multiple threads enqueuing simultaneously must not corrupt the file."""
    from recoil.pipeline._lib.review_queue import enqueue
    queue_path = tmp_path / "review_queue.jsonl"
    errors = []

    def worker(i):
        try:
            enqueue(queue_path=queue_path, project="p", episode_id="E1",
                    shot_id=f"SH{i:03d}", run_id="r1",
                    reason="attempts_exhausted", failure_mode="unknown",
                    attempts=[], total_cost_usd=0.01 * i)
        except Exception as e:
            errors.append(e)

    threads = [threading.Thread(target=worker, args=(i,)) for i in range(20)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()

    assert not errors
    lines = queue_path.read_text().strip().splitlines()
    assert len(lines) == 20
    # Verify each line is valid JSON
    for line in lines:
        json.loads(line)


def test_stale_flagging(tmp_path):
    """Entries older than 7 days are flagged as STALE but never auto-rejected."""
    from recoil.pipeline._lib.review_queue import enqueue, list_pending
    from datetime import datetime, timezone, timedelta
    queue_path = tmp_path / "review_queue.jsonl"
    entry = enqueue(queue_path=queue_path, project="p", episode_id="E1",
                    shot_id="SH01", run_id="r1", reason="attempts_exhausted",
                    failure_mode="unknown", attempts=[], total_cost_usd=0.1)
    # Manually backdate the entry
    lines = queue_path.read_text().strip().splitlines()
    old_entry = json.loads(lines[0])
    old_date = (datetime.now(timezone.utc) - timedelta(days=8)).isoformat()
    old_entry["created_at"] = old_date
    queue_path.write_text(json.dumps(old_entry) + "\n")

    pending = list_pending(queue_path=queue_path, flag_stale_days=7)
    assert len(pending) == 1
    assert pending[0].get("stale") is True


def test_list_pending_empty_file(tmp_path):
    """list_pending on a non-existent file returns empty list."""
    from recoil.pipeline._lib.review_queue import list_pending
    queue_path = tmp_path / "nonexistent.jsonl"
    assert list_pending(queue_path=queue_path) == []


def test_enqueue_rq_id_uses_uuid7(tmp_path):
    """rq_id must be 'rq_' + full uuid7 hex (32 chars)."""
    from recoil.pipeline._lib.review_queue import enqueue
    queue_path = tmp_path / "review_queue.jsonl"
    entry = enqueue(queue_path=queue_path, project="p", episode_id="E1",
                    shot_id="SH01", run_id="r1", reason="test",
                    failure_mode="unknown", attempts=[], total_cost_usd=0.0)
    rq_id = entry["rq_id"]
    assert rq_id.startswith("rq_")
    assert len(rq_id) == 35  # "rq_" + 32 hex chars
    # Hex portion must be valid hex
    int(rq_id[3:], 16)


def test_enqueue_rq_ids_are_unique(tmp_path):
    """Rapid-fire enqueues must produce unique rq_ids."""
    from recoil.pipeline._lib.review_queue import enqueue
    queue_path = tmp_path / "review_queue.jsonl"
    ids = set()
    for i in range(50):
        entry = enqueue(queue_path=queue_path, project="p", episode_id="E1",
                        shot_id=f"SH{i:03d}", run_id="r1", reason="test",
                        failure_mode="unknown", attempts=[], total_cost_usd=0.0)
        ids.add(entry["rq_id"])
    assert len(ids) == 50
