from __future__ import annotations

import threading
import time

import pytest

from pulselog.levels import Level
from pulselog.queue import LogQueue
from pulselog.record import LogRecord


def make_record(msg: str = "test") -> LogRecord:
    return LogRecord(level=Level.INFO, msg=msg, timestamp=time.time(), extra={})


class TestLogQueue:
    def test_put_does_not_block(self):
        q = LogQueue(maxsize=3)
        for i in range(10):
            start = time.perf_counter()
            q.put(make_record(f"msg {i}"))
            elapsed = time.perf_counter() - start
            assert elapsed < 0.001, f"put() blocked for {elapsed:.6f}s"

    def test_overflow_drops_oldest(self):
        q = LogQueue(maxsize=3)
        for i in range(5):
            q.put(make_record(f"msg {i}"))
        batch = q.drain()
        assert len(batch) == 3
        assert batch[0].msg == "msg 2"
        assert batch[1].msg == "msg 3"
        assert batch[2].msg == "msg 4"

    def test_drain_returns_all_and_clears(self):
        q = LogQueue(maxsize=100)
        for i in range(5):
            q.put(make_record(f"msg {i}"))
        batch = q.drain()
        assert len(batch) == 5
        assert q.is_empty()
        assert len(q) == 0

    def test_drain_on_empty_returns_empty_list(self):
        q = LogQueue()
        assert q.drain() == []

    def test_drain_is_atomic(self):
        """Two threads draining simultaneously must not duplicate records."""
        q = LogQueue(maxsize=1000)
        for i in range(100):
            q.put(make_record(f"msg {i}"))

        results = []
        lock = threading.Lock()

        def drainer():
            batch = q.drain()
            with lock:
                results.extend(batch)

        t1 = threading.Thread(target=drainer)
        t2 = threading.Thread(target=drainer)
        t1.start(); t2.start()
        t1.join(); t2.join()

        # No duplicates
        msgs = [r.msg for r in results]
        assert len(msgs) == len(set(msgs)), "Duplicate records found — drain is not atomic"

    def test_len_reflects_queue_size(self):
        q = LogQueue(maxsize=10)
        assert len(q) == 0
        q.put(make_record("a"))
        q.put(make_record("b"))
        assert len(q) == 2
        q.drain()
        assert len(q) == 0

    def test_is_empty(self):
        q = LogQueue()
        assert q.is_empty()
        q.put(make_record())
        assert not q.is_empty()

    def test_dropped_count(self):
        q = LogQueue(maxsize=3)
        for i in range(6):
            q.put(make_record(f"msg {i}"))
        assert q.dropped_count() == 3  # 3 oldest evicted
