# standard
import json

# third party
import pytest

# custom
from sunwaee.modules.gen.session import (
    append_messages,
    create_session,
    delete_session,
    list_sessions,
    load_session,
    update_session_meta,
    _msg_to_dict,
    _dict_to_msg,
)
from sunwaee.modules.gen.engine.types import Message, Role, ToolCall

### FIXTURES


@pytest.fixture
def ws(tmp_path, monkeypatch):
    monkeypatch.setenv("SUNWAEE_WORKSPACES_DIR", str(tmp_path / "workspaces"))
    return "personal"


### HELPERS


def test_msg_to_dict_basic():
    m = Message(role=Role.USER, content="Hello")
    d = _msg_to_dict(m)
    assert d == {"type": "message", "role": "user", "content": "Hello"}


def test_msg_to_dict_with_reasoning():
    m = Message(
        role=Role.ASSISTANT,
        content="Hi",
        reasoning_content="thinking",
        reasoning_signature="sig",
    )
    d = _msg_to_dict(m)
    assert d["reasoning_content"] == "thinking"
    assert d["reasoning_signature"] == "sig"


def test_msg_to_dict_reasoning_omitted_when_none():
    m = Message(role=Role.USER, content="Hi")
    d = _msg_to_dict(m)
    assert "reasoning_content" not in d
    assert "reasoning_signature" not in d


def test_msg_to_dict_tool_result():
    m = Message(role=Role.TOOL, content='{"ok": true}', tool_call_id="call-1")
    d = _msg_to_dict(m)
    assert d["tool_call_id"] == "call-1"


def test_msg_to_dict_tool_calls():
    tc = ToolCall(id="c1", name="tasks", arguments={"action": "list"})
    m = Message(role=Role.ASSISTANT, tool_calls=[tc])
    d = _msg_to_dict(m)
    assert d["tool_calls"] == [
        {"id": "c1", "name": "tasks", "arguments": {"action": "list"}}
    ]


def test_msg_to_dict_tool_calls_with_execution_metadata():
    tc = ToolCall(
        id="c1",
        name="tasks",
        arguments={"action": "list"},
        error=None,
        duration=0.042,
        results=[{"ok": True, "data": []}],
    )
    m = Message(role=Role.ASSISTANT, tool_calls=[tc])
    d = _msg_to_dict(m)
    tc_d = d["tool_calls"][0]
    assert tc_d["duration"] == 0.042
    assert tc_d["results"] == [{"ok": True, "data": []}]
    assert "error" not in tc_d


def test_msg_to_dict_tool_calls_with_error():
    tc = ToolCall(id="c1", name="ghost", arguments={}, error="Unknown tool: 'ghost'")
    m = Message(role=Role.ASSISTANT, tool_calls=[tc])
    d = _msg_to_dict(m)
    tc_d = d["tool_calls"][0]
    assert tc_d["error"] == "Unknown tool: 'ghost'"
    assert "duration" not in tc_d
    assert "results" not in tc_d


def test_toolcall_metadata_round_trips():
    tc = ToolCall(
        id="c1",
        name="tasks",
        arguments={"action": "list"},
        error=None,
        duration=0.1,
        results=[{"ok": True, "data": []}],
    )
    m = Message(role=Role.ASSISTANT, tool_calls=[tc])
    restored = _dict_to_msg(_msg_to_dict(m))
    assert restored.tool_calls is not None
    rtc = restored.tool_calls[0]
    assert rtc.error is None
    assert rtc.duration == 0.1
    assert rtc.results == [{"ok": True, "data": []}]


def test_toolcall_metadata_defaults_on_old_sessions():
    """Old sessions without execution metadata deserialise with safe defaults."""
    d = {
        "type": "message",
        "role": "assistant",
        "content": None,
        "tool_calls": [{"id": "c1", "name": "tasks", "arguments": {}}],
    }
    m = _dict_to_msg(d)
    assert m.tool_calls is not None
    tc = m.tool_calls[0]
    assert tc.error is None
    assert tc.duration == 0.0
    assert tc.results == []


def test_dict_to_msg_basic():
    d = {"type": "message", "role": "user", "content": "Hello"}
    m = _dict_to_msg(d)
    assert m.role == Role.USER
    assert m.content == "Hello"
    assert m.reasoning_content is None
    assert m.reasoning_signature is None


def test_dict_to_msg_with_reasoning():
    d = {
        "type": "message",
        "role": "assistant",
        "content": "Hi",
        "reasoning_content": "I think",
        "reasoning_signature": "s",
    }
    m = _dict_to_msg(d)
    assert m.reasoning_content == "I think"
    assert m.reasoning_signature == "s"


def test_dict_to_msg_with_tool_calls():
    d = {
        "type": "message",
        "role": "assistant",
        "content": None,
        "tool_calls": [{"id": "c1", "name": "notes", "arguments": {"action": "list"}}],
    }
    m = _dict_to_msg(d)
    assert m.tool_calls
    assert len(m.tool_calls) == 1
    assert m.tool_calls[0].name == "notes"


def test_reasoning_round_trip():
    original = Message(
        role=Role.ASSISTANT,
        content="Answer",
        reasoning_content="deep thought",
        reasoning_signature="sig-xyz",
    )
    restored = _dict_to_msg(_msg_to_dict(original))
    assert restored.reasoning_content == "deep thought"
    assert restored.reasoning_signature == "sig-xyz"
    assert restored.content == "Answer"


### TESTS


def test_create_session_returns_session(ws):
    session = create_session(ws)
    assert session.id
    assert session.workspace == ws


def test_create_session_creates_file(ws, tmp_path):
    session = create_session(ws)
    path = tmp_path / "workspaces" / ws / "gen" / "sessions" / f"{session.id}.jsonl"
    assert path.exists()
    with open(path) as f:
        meta = json.loads(f.readline())
    assert meta["type"] == "meta"
    assert meta["id"] == session.id


def test_create_session_with_title(ws):
    session = create_session(ws, title="My session")
    assert session.title == "My session"


def test_load_session_returns_session_and_empty_messages(ws):
    session = create_session(ws, title="Test")
    loaded, messages = load_session(ws, session.id)
    assert loaded.id == session.id
    assert loaded.title == "Test"
    assert messages == []


def test_load_session_not_found(ws):
    with pytest.raises(FileNotFoundError):
        load_session(ws, "nonexistent-id")


def test_append_messages_persists(ws):
    session = create_session(ws)
    msgs = [
        Message(role=Role.USER, content="Hello"),
        Message(role=Role.ASSISTANT, content="Hi"),
    ]
    append_messages(ws, session.id, msgs)

    _, loaded = load_session(ws, session.id)
    assert len(loaded) == 2
    assert loaded[0].role == Role.USER
    assert loaded[0].content == "Hello"
    assert loaded[1].role == Role.ASSISTANT
    assert loaded[1].content == "Hi"


def test_append_messages_with_reasoning(ws):
    session = create_session(ws)
    msgs = [
        Message(
            role=Role.ASSISTANT,
            content="Answer",
            reasoning_content="thinking",
            reasoning_signature="sig",
        )
    ]
    append_messages(ws, session.id, msgs)

    _, loaded = load_session(ws, session.id)
    assert loaded[0].reasoning_content == "thinking"
    assert loaded[0].reasoning_signature == "sig"


def test_append_messages_multiple_batches(ws):
    session = create_session(ws)
    append_messages(ws, session.id, [Message(role=Role.USER, content="First")])
    append_messages(ws, session.id, [Message(role=Role.ASSISTANT, content="Second")])

    _, loaded = load_session(ws, session.id)
    assert len(loaded) == 2
    assert loaded[1].content == "Second"


def test_append_preserves_tool_calls(ws):
    session = create_session(ws)
    tc = ToolCall(id="c1", name="tasks", arguments={"action": "list"})
    msgs = [Message(role=Role.ASSISTANT, tool_calls=[tc])]
    append_messages(ws, session.id, msgs)

    _, loaded = load_session(ws, session.id)
    assert loaded[0].tool_calls
    assert loaded[0].tool_calls[0].name == "tasks"


def test_update_session_meta_rewrites_first_line(ws):
    session = create_session(ws)
    append_messages(ws, session.id, [Message(role=Role.USER, content="Hi")])

    session.title = "Updated title"
    update_session_meta(ws, session)

    loaded, msgs = load_session(ws, session.id)
    assert loaded.title == "Updated title"
    assert len(msgs) == 1  # messages preserved


def test_list_sessions_empty(ws):
    assert list_sessions(ws) == []


def test_list_sessions_returns_all(ws):
    s1 = create_session(ws, title="First")
    s2 = create_session(ws, title="Second")
    sessions = list_sessions(ws)
    ids = {s.id for s in sessions}
    assert s1.id in ids
    assert s2.id in ids


def test_list_sessions_sorted_by_mtime(ws, tmp_path):
    import time

    s1 = create_session(ws, title="Older")
    time.sleep(0.05)
    s2 = create_session(ws, title="Newer")
    sessions = list_sessions(ws)
    # most recent first
    assert sessions[0].id == s2.id
    assert sessions[1].id == s1.id


def test_delete_session(ws, tmp_path):
    session = create_session(ws)
    delete_session(ws, session.id)
    path = tmp_path / "workspaces" / ws / "gen" / "sessions" / f"{session.id}.jsonl"
    assert not path.exists()


def test_delete_session_not_found(ws):
    with pytest.raises(FileNotFoundError):
        delete_session(ws, "ghost-id")


def test_delete_session_not_loadable_after(ws):
    session = create_session(ws)
    delete_session(ws, session.id)
    with pytest.raises(FileNotFoundError):
        load_session(ws, session.id)
