from __future__ import annotations

from pathlib import Path

import pytest

from yee88.backends import EngineConfig
from yee88.config import ConfigError
from yee88.events import EventFactory
from yee88.model import ActionEvent, CompletedEvent, StartedEvent
from yee88.runners.codex import (
    CodexRunner,
    _format_change_summary,
    _normalize_change_list,
    _parse_reconnect_message,
    _short_tool_name,
    _summarize_todo_list,
    _summarize_tool_result,
    _todo_title,
    build_runner,
    find_exec_only_flag,
    translate_codex_event,
)
from yee88.schemas import codex as codex_schema


def test_codex_helper_functions() -> None:
    assert find_exec_only_flag(["--json"]) == "--json"
    assert find_exec_only_flag(["--output-schema=foo"]) == "--output-schema=foo"
    assert find_exec_only_flag(["--model", "gpt-4"]) is None

    assert _parse_reconnect_message("Reconnecting... 2/5") == (2, 5)
    assert _parse_reconnect_message("Reconnecting... x/y") is None
    assert _parse_reconnect_message("nope") is None

    assert _short_tool_name("docs", "search") == "docs.search"
    assert _short_tool_name(None, "search") == "search"
    assert _short_tool_name(None, None) == "tool"

    summary = _summarize_tool_result({"content": ["hi"], "structured": {"ok": True}})
    assert summary == {"content_blocks": 1, "has_structured": True}
    summary = _summarize_tool_result({"content": "hello", "structured_content": None})
    assert summary == {"content_blocks": 1, "has_structured": False}
    assert _summarize_tool_result({"other": 1}) is None

    changes = [
        codex_schema.FileUpdateChange(path="a.txt", kind="update"),
        {"path": "b.txt", "kind": "delete"},
        {"path": ""},
    ]
    assert _normalize_change_list(changes) == [
        {"path": "a.txt", "kind": "update"},
        {"path": "b.txt", "kind": "delete"},
    ]
    assert _format_change_summary(changes) == "a.txt, b.txt"
    assert _format_change_summary([{"path": ""}]) == "1 files"


def test_summarize_todo_list_and_title() -> None:
    items = [
        codex_schema.TodoItem(text="first", completed=True),
        codex_schema.TodoItem(text="next", completed=False),
        {"text": "later", "completed": False},
    ]
    summary = _summarize_todo_list(items)
    assert summary.done == 1
    assert summary.total == 3
    assert summary.next_text == "next"
    assert _todo_title(summary) == "todo 1/3: next"

    done_summary = _summarize_todo_list([{"text": "done", "completed": True}])
    assert _todo_title(done_summary) == "todo 1/1: done"
    assert _todo_title(_summarize_todo_list("nope")) == "todo"


def test_translate_codex_events_for_items() -> None:
    factory = EventFactory("codex")
    event = codex_schema.ItemStarted(
        item=codex_schema.WebSearchItem(id="w1", query="query")
    )
    out = translate_codex_event(event, title="Codex", factory=factory)
    assert len(out) == 1
    assert isinstance(out[0], ActionEvent)
    assert out[0].action.kind == "web_search"
    assert out[0].phase == "started"

    event = codex_schema.ItemCompleted(
        item=codex_schema.WebSearchItem(id="w1", query="query")
    )
    out = translate_codex_event(event, title="Codex", factory=factory)
    assert isinstance(out[0], ActionEvent)
    assert out[0].phase == "completed"
    assert out[0].ok is True

    event = codex_schema.ItemStarted(
        item=codex_schema.ReasoningItem(id="r1", text="thinking")
    )
    out = translate_codex_event(event, title="Codex", factory=factory)
    assert isinstance(out[0], ActionEvent)
    assert out[0].action.kind == "note"
    assert out[0].action.title == "thinking"

    event = codex_schema.ItemUpdated(
        item=codex_schema.TodoListItem(
            id="t1",
            items=[
                codex_schema.TodoItem(text="todo one", completed=False),
                codex_schema.TodoItem(text="todo two", completed=True),
            ],
        )
    )
    out = translate_codex_event(event, title="Codex", factory=factory)
    assert isinstance(out[0], ActionEvent)
    assert out[0].action.detail["done"] == 1
    assert out[0].action.detail["total"] == 2
    assert "todo 1/2" in out[0].action.title

    started = codex_schema.ItemStarted(
        item=codex_schema.ErrorItem(id="e1", message="boom")
    )
    assert translate_codex_event(started, title="Codex", factory=factory) == []

    completed = codex_schema.ItemCompleted(
        item=codex_schema.ErrorItem(id="e1", message="boom")
    )
    out = translate_codex_event(completed, title="Codex", factory=factory)
    assert isinstance(out[0], ActionEvent)
    assert out[0].action.kind == "warning"
    assert out[0].ok is False


def test_translate_codex_thread_started() -> None:
    factory = EventFactory("codex")
    event = codex_schema.ThreadStarted(thread_id="sess-1")
    out = translate_codex_event(event, title="Codex", factory=factory)
    assert len(out) == 1
    assert isinstance(out[0], StartedEvent)
    assert out[0].resume.value == "sess-1"


def test_codex_runner_translate_reconnect_message() -> None:
    runner = CodexRunner(codex_cmd="codex", extra_args=[])
    state = runner.new_state("hi", None)
    event = codex_schema.StreamError(message="Reconnecting... 2/3")
    out = runner.translate(event, state=state, resume=None, found_session=None)
    assert len(out) == 1
    assert isinstance(out[0], ActionEvent)
    assert out[0].phase == "updated"
    assert out[0].action.detail["attempt"] == 2
    assert out[0].action.detail["max"] == 3


def test_codex_runner_process_and_stream_end_events() -> None:
    runner = CodexRunner(codex_cmd="codex", extra_args=[])
    state = runner.new_state("hi", None)

    out = runner.process_error_events(2, resume=None, found_session=None, state=state)
    assert len(out) == 2
    completed = out[-1]
    assert isinstance(completed, CompletedEvent)
    assert completed.ok is False

    end = runner.stream_end_events(resume=None, found_session=None, state=state)
    assert len(end) == 1
    end_event = end[0]
    assert isinstance(end_event, CompletedEvent)
    assert end_event.ok is False

    started = translate_codex_event(
        codex_schema.ThreadStarted(thread_id="sess-2"),
        title="Codex",
        factory=EventFactory("codex"),
    )[0]
    assert isinstance(started, StartedEvent)
    end = runner.stream_end_events(
        resume=None,
        found_session=started.resume,
        state=state,
    )
    end_event = end[0]
    assert isinstance(end_event, CompletedEvent)
    assert end_event.ok is True


def test_codex_build_runner_configs(tmp_path: Path) -> None:
    cfg: EngineConfig = {}
    runner = build_runner(cfg, tmp_path)
    assert isinstance(runner, CodexRunner)
    assert runner.extra_args == ["-c", "notify=[]"]

    cfg = {"extra_args": ["--foo"], "profile": "Demo"}
    runner = build_runner(cfg, tmp_path)
    assert isinstance(runner, CodexRunner)
    assert runner.extra_args[-2:] == ["--profile", "Demo"]
    assert runner.session_title == "Demo"

    with pytest.raises(ConfigError):
        build_runner({"extra_args": ["--json"]}, tmp_path)

    with pytest.raises(ConfigError):
        build_runner({"extra_args": ["--foo", 1]}, tmp_path)

    with pytest.raises(ConfigError):
        build_runner({"profile": 123}, tmp_path)
