"""The conversation transcript log (docs/specs/2026-08-10-ask-gpt-resume-live-design.md
§4): canonical is authoritative, the cwd duplicate is a real file rewritten in full
after every turn, and a crash leaves a marker the next run repairs before appending."""

from __future__ import annotations

import pytest

import convlog

CID = "33333333-3333-4333-8333-333333333333"


@pytest.fixture(autouse=True)
def isolated_state(monkeypatch, tmp_path):
    monkeypatch.setattr(convlog, "STATE", tmp_path / "gptbridge")
    monkeypatch.setattr(convlog, "LOGS", tmp_path / "gptbridge" / "logs")
    return tmp_path


def make_turn(number=1, **overrides):
    fields = dict(number=number, timestamp="2026-08-11T10:00:00", effort="medium",
                 prompt="hi", thinking="thought", answer="hello", artifacts=["/tmp/a.png"])
    fields.update(overrides)
    return convlog.Turn(**fields)


def test_create_then_append_round_trips_through_parse():
    log = convlog.ConversationLog(CID)
    log.ensure(url=f"https://chatgpt.com/c/{CID}")
    log.append(make_turn(1))
    log.append(make_turn(2, prompt="again", answer="again reply"))
    turns = log.turns()
    assert [t.number for t in turns] == [1, 2]
    assert turns[0].prompt == "hi" and turns[0].answer == "hello"
    assert turns[0].artifacts == ["/tmp/a.png"]
    assert turns[1].prompt == "again"
    assert not turns[0].interrupted and not turns[0].hydrated


def test_duplicate_is_rewritten_in_full_and_is_a_real_file(tmp_path):
    log = convlog.ConversationLog(CID)
    log.ensure(url="u")
    log.append(make_turn(1))
    cwd = tmp_path / "work"
    cwd.mkdir()
    dest = log.rewrite_duplicate(cwd)
    assert dest == cwd / "ask-gpt" / f"{CID}.log"
    assert not dest.is_symlink()
    assert dest.read_text() == log.path.read_text()
    log.append(make_turn(2))
    log.rewrite_duplicate(cwd)
    assert dest.read_text() == log.path.read_text()
    assert "turn 2" in dest.read_text()


def test_a_stale_duplicate_is_repaired_the_next_time_that_cwd_is_used(tmp_path):
    log = convlog.ConversationLog(CID)
    log.ensure(url="u")
    log.append(make_turn(1))
    cwd = tmp_path / "work"
    cwd.mkdir()
    stale = log.rewrite_duplicate(cwd)
    log.append(make_turn(2))  # a second run, from elsewhere, that never touched `cwd`
    assert "turn 2" not in stale.read_text()
    log.rewrite_duplicate(cwd)  # `cwd` used again: its duplicate catches up
    assert "turn 2" in stale.read_text()


def test_an_unclosed_trailing_block_is_annotated_interrupted_before_any_append():
    log = convlog.ConversationLog(CID)
    log.ensure(url="u")
    log.append(make_turn(1))
    # simulate a crash mid-write: the end marker for turn 2 never landed
    with log.path.open("a") as handle:
        handle.write("## turn 2 — 2026-08-11T10:05:00 (effort: medium)\n\n"
                     "### prompt\nunfinished\n")
    log.ensure(url="u")  # the next invocation's open() call
    turns = log.turns()
    assert turns[1].interrupted
    assert "(interrupted)" in log.path.read_text()
    log.ensure(url="u")  # idempotent: a second ensure() does not double-tag it
    assert log.path.read_text().count("(interrupted)") == 1


def test_pre_id_failure_writes_a_stamped_log_not_the_conversation_log():
    path = convlog.write_failure(
        "2026-08-11-100000",
        {"phase": "attaching", "thinking": "", "answer": "", "saved_artifacts": []},
        "the prompt", "high")
    assert path.name == "failed-2026-08-11-100000.log"
    text = path.read_text()
    assert "the prompt" in text
    assert "phase: attaching" in text
    assert "(interrupted)" in text


def test_failure_flush_records_partial_thinking_answer_and_artifacts():
    path = convlog.write_failure(
        "s", {"phase": "streaming", "thinking": "partial thought", "answer": "partial ans",
             "saved_artifacts": ["/tmp/x.png"]}, "prompt text", None)
    text = path.read_text()
    assert "partial thought" in text and "partial ans" in text and "/tmp/x.png" in text


def test_locking_bounds_the_wait_and_names_the_conversation():
    with convlog.held(CID, timeout=0.2):
        with pytest.raises(convlog.ConvLogError, match=CID):
            with convlog.held(CID, timeout=0.2):
                pass


def test_hydrate_appends_only_turns_beyond_what_the_log_already_holds():
    log = convlog.ConversationLog(CID)
    log.ensure(url="u")
    log.append(make_turn(1))
    dom_turns = [
        {"prompt": "hi", "answer": "hello", "thinking": ""},
        {"prompt": "foreign turn", "answer": "foreign reply", "thinking": ""},
    ]
    added = convlog.hydrate(log, dom_turns)
    assert len(added) == 1
    assert added[0].prompt == "foreign turn"
    assert added[0].hydrated
    turns = log.turns()
    assert len(turns) == 2 and turns[1].hydrated


def test_hydrate_on_an_empty_log_appends_every_foreign_turn():
    log = convlog.ConversationLog(CID)
    log.ensure(url="u")
    dom_turns = [{"prompt": "a", "answer": "1", "thinking": ""},
                {"prompt": "b", "answer": "2", "thinking": ""}]
    added = convlog.hydrate(log, dom_turns)
    assert [t.number for t in added] == [1, 2]
    assert all(t.hydrated for t in added)
