from __future__ import annotations

import io
import json
import threading
import time
from pathlib import Path

from adw_modules.data_types import EventRecord
from adw_modules.tracer import Tracer
from adw_modules.watch import open_readonly, watch


def traced(tmp_path: Path) -> tuple[Tracer, Path]:
    db = tmp_path / "trace.db"
    return Tracer(db, tmp_path / "events.jsonl"), db


def test_follow_emits_new_events_once_and_never_reemits(tmp_path: Path) -> None:
    tracer, db = traced(tmp_path)
    tracer.session_start("live", "test")
    output = io.StringIO()
    ready = threading.Event()

    def reader() -> None:
        conn = open_readonly(db)
        try:
            watch(conn, "live", follow=True, poll_ms=5, output=output, _ready=ready)
        finally:
            conn.close()

    thread = threading.Thread(target=reader)
    thread.start()
    assert ready.wait(timeout=2)
    first = tracer.event(EventRecord(adw_id="live", type="log", name="one", payload={"message": "first"}))
    second = tracer.event(EventRecord(adw_id="live", type="log", name="two", payload={"message": "second"}))
    time.sleep(0.03)
    tracer.session_finish("live", True)
    thread.join(timeout=2)
    assert not thread.is_alive()
    text = output.getvalue()
    assert text.count(first) == 0  # human output intentionally does not expose internal event ids
    assert text.count("log one:") == 1
    assert text.count("log two:") == 1
    assert "first" in text and "second" in text
    assert first != second


def test_session_with_no_events_is_valid(tmp_path: Path) -> None:
    tracer, db = traced(tmp_path)
    tracer.session_start("empty", "test")
    tracer.session_finish("empty", True)
    output = io.StringIO()
    conn = open_readonly(db)
    try:
        assert watch(conn, "empty", follow=False, since_start=True, output=output) == 0
    finally:
        conn.close()
    assert "session empty" in output.getvalue()


def test_missing_artifact_renders_absent(tmp_path: Path) -> None:
    tracer, db = traced(tmp_path)
    tracer.session_start("artifact", "test")
    missing = tmp_path / "never-created.log"
    tracer.event(EventRecord(adw_id="artifact", type="tool_call", name="quality:test",
                             payload={"output_artifact": str(missing), "passed": False,
                                      "returncode": 1, "command": "pytest"}))
    tracer.session_finish("artifact", False)
    output = io.StringIO()
    conn = open_readonly(db)
    try:
        watch(conn, "artifact", follow=False, since_start=True, output=output)
    finally:
        conn.close()
    assert f"artifact={missing} (absent)" in output.getvalue()


def test_json_is_one_object_per_line(tmp_path: Path) -> None:
    tracer, db = traced(tmp_path)
    tracer.session_start("json", "test")
    tracer.event(EventRecord(adw_id="json", type="error", name="boom",
                             payload={"error": "real failure"}))
    tracer.session_finish("json", False)
    output = io.StringIO()
    conn = open_readonly(db)
    try:
        watch(conn, "json", follow=False, since_start=True, json_output=True, output=output)
    finally:
        conn.close()
    objects = [json.loads(line) for line in output.getvalue().splitlines()]
    assert [item["kind"] for item in objects] == ["session", "event"]
    assert objects[1]["payload"]["error"] == "real failure"


def test_implicit_selection_prefers_running_over_newer_finished(tmp_path: Path) -> None:
    tracer, db = traced(tmp_path)
    tracer.session_start("running", "test")
    tracer.session_start("newer-finished", "test")
    tracer.session_finish("newer-finished", True)
    output = io.StringIO()
    conn = open_readonly(db)
    try:
        watch(conn, follow=False, output=output)
    finally:
        conn.close()
    assert "session running" in output.getvalue()
    assert "most recent running session" in output.getvalue()
