"""Tests for spens.sessions (shared session-artifact loading).

The chat-URL, JSON/NDJSON and request-log cases were previously in
test_viewer.py, from where these loaders were extracted.
"""

from __future__ import annotations

import json
from pathlib import Path

from spens.sessions import (
    is_chat_request,
    is_chat_url,
    load_audit_session,
    load_nono_session_meta,
    load_request_log,
    load_trace_index,
    read_json,
    read_ndjson,
)


def _write(path: Path, text: str) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(text, encoding="utf-8")


# -- raw readers -------------------------------------------------------------


def test_read_json(tmp_path: Path) -> None:
    p = tmp_path / "data.json"
    p.write_text('{"a": 1}', encoding="utf-8")
    assert read_json(p) == {"a": 1}
    p.write_text("not json", encoding="utf-8")
    assert read_json(p) is None
    assert read_json(tmp_path / "missing.json") is None


def test_read_ndjson(tmp_path: Path) -> None:
    p = tmp_path / "events.ndjson"
    p.write_text('{"a": 1}\n\nnot json\n{"b": 2}\n', encoding="utf-8")
    assert read_ndjson(p) == [{"a": 1}, {"b": 2}]
    assert read_ndjson(tmp_path / "missing.ndjson") == []


def test_read_ndjson_tolerates_truncated_final_line(tmp_path: Path) -> None:
    """A live producer may be mid-write; earlier rows must still load."""
    p = tmp_path / "events.ndjson"
    p.write_text('{"a": 1}\n{"b": 2}\n{"c": ', encoding="utf-8")
    assert read_ndjson(p) == [{"a": 1}, {"b": 2}]


# -- chat detection ----------------------------------------------------------


def test_chat_urls() -> None:
    for url in [
        "https://api.openai.com/v1/chat/completions",
        "https://api.anthropic.com/v1/messages",
        "https://api.openai.com/v1/responses",
    ]:
        assert is_chat_url(url), url


def test_non_chat_urls() -> None:
    for url in ["https://models.opencode.ai/api.json", "https://example.com/index.html"]:
        assert not is_chat_url(url), url


def test_chat_url_requires_a_path_boundary() -> None:
    """Lookalike paths must not be treated as completion endpoints."""
    assert not is_chat_url("https://api.example.com/v1/messages_archive")
    assert not is_chat_url("https://api.example.com/v1/responsesx")


def test_is_chat_request_needs_url_and_body() -> None:
    url = "https://api.anthropic.com/v1/messages"
    assert is_chat_request(url, {"messages": []})
    assert is_chat_request("https://api.openai.com/v1/responses", {"input": "hi"})
    # Right URL, wrong body shape -- some other call against the same host.
    assert not is_chat_request(url, {"model": "x"})
    assert not is_chat_request(url, None)
    assert not is_chat_request(url, "not a dict")
    # Right body shape, unrelated URL.
    assert not is_chat_request("https://example.com/a.json", {"messages": []})


# -- nono audit artifacts ----------------------------------------------------


def test_load_nono_session_meta(tmp_path: Path) -> None:
    _write(tmp_path / "nono-audit" / "sessions" / "abc.json", '{"name": "run", "exit_code": 0}')
    assert load_nono_session_meta(tmp_path) == {"name": "run", "exit_code": 0}


def test_load_nono_session_meta_missing(tmp_path: Path) -> None:
    assert load_nono_session_meta(tmp_path) is None


def test_load_audit_session(tmp_path: Path) -> None:
    audit = tmp_path / "nono-audit" / "audit" / "deadbeef"
    _write(audit / "session.json", '{"ended": "2026-01-01T00:00:05Z"}')
    _write(audit / "audit-events.ndjson", '{"event": "one"}\n{"event": "two"}\n')
    sess, events = load_audit_session(tmp_path)
    assert sess == {"ended": "2026-01-01T00:00:05Z"}
    assert len(events) == 2


def test_load_audit_session_missing(tmp_path: Path) -> None:
    assert load_audit_session(tmp_path) == (None, [])


def test_request_log(tmp_path: Path) -> None:
    session_dir = tmp_path / "sess1"
    _write(session_dir / "traces" / "request_log.jsonl",
           '{"timestamp": "2026-01-01T00:00:00Z", "method": "GET", "url": "https://example.com", "status_code": 200}\n'
           '{"timestamp": "2026-01-01T00:00:01Z", "method": "POST", "url": "https://api.github.com/repos", "status_code": 403}\n')
    entries = load_request_log(session_dir)
    assert len(entries) == 2
    assert entries[0]["method"] == "GET"
    assert entries[0]["status_code"] == 200
    assert entries[1]["method"] == "POST"
    assert entries[1]["status_code"] == 403


def test_request_log_missing(tmp_path: Path) -> None:
    assert load_request_log(tmp_path / "nope") == []


# -- trace index -------------------------------------------------------------


def test_load_trace_index_missing_dir(tmp_path: Path) -> None:
    index = load_trace_index(tmp_path)
    assert index.ordered_ids == []
    assert index.requests == {}


def test_load_trace_index_groups_by_request_id(tmp_path: Path) -> None:
    rows = [
        {"type": "request", "id": "r1", "timestamp": "2026-01-01T00:00:00Z", "url": "u1"},
        {"type": "response", "request_id": "r1", "timestamp": "2026-01-01T00:00:01Z", "status_code": 200},
        {"type": "request", "id": "r2", "timestamp": "2026-01-01T00:00:02Z", "url": "u2"},
        {"type": "response_chunk", "request_id": "r2", "timestamp": "2026-01-01T00:00:03Z", "content": {"a": 1}},
        {"type": "response_chunk", "request_id": "r2", "timestamp": "2026-01-01T00:00:04Z", "content": {"a": 2}},
        {"type": "response_meta", "request_id": "r2", "timestamp": "2026-01-01T00:00:05Z", "status_code": 200},
    ]
    _write(
        tmp_path / "traces" / "captured.jsonl",
        "".join(json.dumps(r) + "\n" for r in rows),
    )
    index = load_trace_index(tmp_path)
    assert index.ordered_ids == ["r1", "r2"]
    assert index.requests["r1"]["url"] == "u1"
    assert index.responses["r1"]["status_code"] == 200
    assert len(index.chunks["r2"]) == 2
    assert index.metas["r2"]["status_code"] == 200
    assert "r2" not in index.responses


def test_load_trace_index_orders_across_files_by_timestamp(tmp_path: Path) -> None:
    """Rows are merged from every *.jsonl and ordered by capture time."""
    traces = tmp_path / "traces"
    _write(traces / "b.jsonl",
           json.dumps({"type": "request", "id": "second", "timestamp": "2026-01-01T00:00:02Z"}) + "\n")
    _write(traces / "a.jsonl",
           json.dumps({"type": "request", "id": "first", "timestamp": "2026-01-01T00:00:01Z"}) + "\n")
    assert load_trace_index(tmp_path).ordered_ids == ["first", "second"]
