"""Tests for the copilot-session adapter."""

from __future__ import annotations

import json
import os
import time
from datetime import UTC, datetime
from pathlib import Path

import pytest

from copilot_session import CopilotConfig, CopilotSource
from session_store import SessionExport, SessionInfo
from session_store.errors import SessionLoadError, SessionNotFoundError

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def write_jsonl(path: Path, records: list[dict]) -> None:
    """Write a list of dicts as JSONL (one JSON object per line)."""
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("w", encoding="utf-8") as fh:
        for rec in records:
            fh.write(json.dumps(rec) + "\n")


def write_workspace_yaml(
    path: Path,
    title: str = "Untitled",
    working_directory: str = "",
    last_activity: str = "2026-07-14T10:30:00Z",
    created_at: str = "2026-07-14T08:00:00Z",
    session_id: str | None = None,
    summary_count: int | None = None,
    git_root: str | None = None,
    branch: str | None = None,
) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    lines = [
        f'title: "{title}"',
        f'working_directory: "{working_directory}"',
        f'created_at: "{created_at}"',
        f'last_activity: "{last_activity}"',
    ]
    if session_id is not None:
        lines.append(f'id: "{session_id}"')
    if summary_count is not None:
        lines.append(f"summary_count: {summary_count}")
    if git_root is not None:
        lines.append(f'gitRoot: "{git_root}"')
    if branch is not None:
        lines.append(f'branch: "{branch}"')
    path.write_text("\n".join(lines) + "\n", encoding="utf-8")


def make_source(tmp_dir: Path) -> CopilotSource:
    return CopilotSource(config=CopilotConfig(session_dir=Path(tmp_dir)))


def msg(
    role: str,
    content: str,
    message_id: str,
    created_at: str = "2026-07-14T08:00:00Z",
    **extra: object,
) -> dict:
    result: dict = {
        "role": role,
        "content": content,
        "message_id": message_id,
        "created_at": created_at,
    }
    result.update(extra)
    return result


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def session_state(tmp_path: Path) -> Path:
    """Create a temp session-state directory with sample data."""
    state = tmp_path / "session-state"
    state.mkdir()

    # Newer format: sess-abc.jsonl
    write_jsonl(
        state / "sess-abc.jsonl",
        [
            msg("user", "Hello", "msg-1"),
            msg("assistant", "Hi there", "msg-2", toolRequests=[{"id": "tool-1"}]),
        ],
    )

    # Older format: sess-xyz/ subdirectory with workspace.yaml + checkpoints.
    write_workspace_yaml(
        state / "sess-xyz" / "workspace.yaml",
        title="Implementing the auth module",
        working_directory="/root/tools/my-project",
        last_activity="2026-07-14T10:30:00Z",
        session_id="sess-xyz",
        summary_count=5,
        git_root="/root/tools/my-project",
        branch="main",
    )
    write_jsonl(
        state / "sess-xyz" / "checkpoints" / "sess-xyz.jsonl",
        [
            msg("user", "Fix the bug", "msg-1"),
            msg("assistant", "Done", "msg-2"),
        ],
    )

    return state


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------


def test_tool_name_returns_copilot(session_state: Path) -> None:
    source = make_source(session_state)
    assert source.tool_name() == "copilot"


def test_list_sessions_returns_both_formats(session_state: Path) -> None:
    source = make_source(session_state)
    sessions = source.list_sessions(limit=50)
    ids = {s.session_id for s in sessions}
    assert "sess-abc" in ids
    assert "sess-xyz" in ids
    for s in sessions:
        assert isinstance(s, SessionInfo)
        assert s.tool == "copilot"


def test_list_sessions_returns_empty_when_dir_missing(tmp_path: Path) -> None:
    source = make_source(tmp_path / "does-not-exist")
    assert source.list_sessions() == []


def test_list_sessions_reads_workspace_yaml(session_state: Path) -> None:
    source = make_source(session_state)
    sessions = {s.session_id: s for s in source.list_sessions(limit=50)}
    xyz = sessions["sess-xyz"]
    assert xyz.title == "Implementing the auth module"
    assert xyz.working_directory == "/root/tools/my-project"
    assert xyz.message_count == 2


def test_list_sessions_jsonl_uses_untitled_and_mtime(session_state: Path) -> None:
    source = make_source(session_state)
    sessions = {s.session_id: s for s in source.list_sessions(limit=50)}
    abc = sessions["sess-abc"]
    assert abc.title == "Untitled"
    assert abc.message_count == 2
    assert abc.working_directory == ""


def test_list_sessions_sorted_by_last_activity_desc(session_state: Path) -> None:
    source = make_source(session_state)
    sessions = source.list_sessions(limit=50)
    activities = [s.last_activity for s in sessions]
    assert activities == sorted(activities, reverse=True)


def test_load_session_from_jsonl_file(session_state: Path) -> None:
    source = make_source(session_state)
    export = source.load_session("sess-abc")
    assert isinstance(export, SessionExport)
    assert export.session_id == "sess-abc"
    assert export.tool == "copilot"
    assert len(export.messages) == 2
    assert export.messages[0].role == "user"
    assert export.messages[0].content == "Hello"
    assert export.messages[1].role == "assistant"
    assert export.messages[0].session_id == "sess-abc"
    assert export.messages[0].tool == "copilot"
    # created_at / last_activity_at derived from messages.
    assert export.created_at is not None
    assert export.last_activity_at is not None
    assert export.model is None
    # toolRequests preserved in message metadata.
    assert export.messages[1].metadata.get("toolRequests") == [{"id": "tool-1"}]
    # Messages without extra fields have empty metadata.
    assert export.messages[0].metadata == {}


def test_load_session_from_subdirectory(session_state: Path) -> None:
    source = make_source(session_state)
    export = source.load_session("sess-xyz")
    assert export.session_id == "sess-xyz"
    assert export.title == "Implementing the auth module"
    assert export.working_directory == "/root/tools/my-project"
    assert len(export.messages) == 2
    assert export.messages[0].content == "Fix the bug"
    # Session-level fields from workspace.yaml.
    assert export.created_at == datetime(2026, 7, 14, 8, 0, 0, tzinfo=UTC)
    assert export.last_activity_at == datetime(2026, 7, 14, 10, 30, 0, tzinfo=UTC)
    assert export.model is None
    # Tool-specific session fields preserved in metadata.
    assert export.metadata.get("id") == "sess-xyz"
    assert export.metadata.get("summary_count") == 5
    assert export.metadata.get("gitRoot") == "/root/tools/my-project"
    assert export.metadata.get("branch") == "main"


def test_load_session_raises_not_found_for_unknown(session_state: Path) -> None:
    source = make_source(session_state)
    with pytest.raises(SessionNotFoundError):
        source.load_session("does-not-exist")


def test_load_session_raises_load_error_when_dir_missing(tmp_path: Path) -> None:
    source = make_source(tmp_path / "missing")
    with pytest.raises(SessionLoadError):
        source.load_session("sess-abc")


def test_detect_current_session_finds_most_recent(session_state: Path) -> None:
    # Touch sess-abc.jsonl so it is the newest entry.
    abc = session_state / "sess-abc.jsonl"
    os.utime(abc, (time.time() + 100, time.time() + 100))
    source = make_source(session_state)
    current = source.detect_current_session()
    assert current == "sess-abc"


def test_detect_current_session_returns_none_when_dir_missing(tmp_path: Path) -> None:
    source = make_source(tmp_path / "missing")
    assert source.detect_current_session() is None


def test_detect_current_session_returns_none_when_empty(tmp_path: Path) -> None:
    state = tmp_path / "empty-state"
    state.mkdir()
    source = make_source(state)
    assert source.detect_current_session() is None


def test_jsonl_line_parse_failure_skipped(tmp_path: Path) -> None:
    state = tmp_path / "session-state"
    state.mkdir()
    jsonl = state / "sess-bad.jsonl"
    jsonl.write_text(
        json.dumps(msg("user", "Hello", "msg-1"))
        + "\n{not valid json}\n"
        + json.dumps(msg("assistant", "Hi", "msg-2"))
        + "\n",
        encoding="utf-8",
    )
    source = make_source(state)
    export = source.load_session("sess-bad")
    assert len(export.messages) == 2
    contents = [m.content for m in export.messages]
    assert "Hello" in contents
    assert "Hi" in contents


def test_empty_jsonl_returns_empty_messages(tmp_path: Path) -> None:
    state = tmp_path / "session-state"
    state.mkdir()
    jsonl = state / "sess-empty.jsonl"
    jsonl.write_text("", encoding="utf-8")
    source = make_source(state)
    export = source.load_session("sess-empty")
    assert export.messages == []
    assert export.session_id == "sess-empty"


def test_load_session_workspace_yaml_missing_uses_defaults(tmp_path: Path) -> None:
    state = tmp_path / "session-state"
    state.mkdir()
    # Subdirectory with a jsonl but no workspace.yaml.
    write_jsonl(
        state / "sess-noyaml" / "sess-noyaml.jsonl",
        [msg("user", "Hi", "msg-1")],
    )
    source = make_source(state)
    export = source.load_session("sess-noyaml")
    assert export.title == "Untitled"
    assert export.working_directory == ""
    assert len(export.messages) == 1


def test_parse_datetime_handles_z_suffix() -> None:
    from copilot_session.parser import parse_datetime

    dt = parse_datetime("2026-07-14T08:00:00Z")
    assert dt.year == 2026
    assert dt.month == 7
    assert dt.day == 14
    assert dt.tzinfo is not None


def test_parse_datetime_fallback_for_none() -> None:
    from copilot_session.parser import parse_datetime

    dt = parse_datetime(None)
    assert dt.tzinfo is not None
    # Should be close to now.
    now = datetime.now(UTC)
    assert abs((now - dt).total_seconds()) < 5
