from __future__ import annotations

import queue
import sqlite3
from collections import OrderedDict
from pathlib import Path

import pytest


pytestmark = pytest.mark.requires_agent_modules


CLI_PROMPT = "Reply with exactly CLI-ORIGIN-OK and nothing else."
CLI_REPLY = "CLI-ORIGIN-OK"
WEBUI_FOLLOWUP = "What exact string did you just reply with? Answer with only that string."


def _make_cli_continuation_state_db(path: Path, *, parent_sid: str, child_sid: str, workspace: str) -> None:
    conn = sqlite3.connect(path)
    conn.execute(
        """
        CREATE TABLE sessions (
            id TEXT PRIMARY KEY,
            source TEXT,
            title TEXT,
            model TEXT,
            cwd TEXT,
            started_at REAL,
            ended_at REAL,
            end_reason TEXT,
            parent_session_id TEXT,
            message_count INTEGER
        )
        """
    )
    conn.execute(
        """
        CREATE TABLE messages (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            session_id TEXT,
            role TEXT,
            content TEXT,
            timestamp REAL
        )
        """
    )
    conn.execute(
        """
        INSERT INTO sessions (
            id, source, title, model, cwd, started_at, ended_at, end_reason, parent_session_id, message_count
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """,
        (parent_sid, "cli", "CLI parent", "test-model", workspace, 1.0, 2.0, "cli_close", None, 2),
    )
    conn.execute(
        """
        INSERT INTO sessions (
            id, source, title, model, cwd, started_at, ended_at, end_reason, parent_session_id, message_count
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """,
        (child_sid, "cli", "CLI child", "test-model", workspace, 3.0, None, None, parent_sid, 0),
    )
    conn.execute(
        "INSERT INTO messages (session_id, role, content, timestamp) VALUES (?, ?, ?, ?)",
        (parent_sid, "user", CLI_PROMPT, 1.0),
    )
    conn.execute(
        "INSERT INTO messages (session_id, role, content, timestamp) VALUES (?, ?, ?, ?)",
        (parent_sid, "assistant", CLI_REPLY, 2.0),
    )
    conn.commit()
    conn.close()


def _install_cli_continuity_env(monkeypatch, tmp_path):
    import api.config as config
    import api.models as models
    import api.profiles as profiles
    import api.routes as routes
    import api.streaming as streaming

    session_dir = tmp_path / "sessions"
    session_dir.mkdir()
    index_file = session_dir / "_index.json"
    state_db = tmp_path / "state.db"

    monkeypatch.setattr(models, "SESSION_DIR", session_dir)
    monkeypatch.setattr(models, "SESSION_INDEX_FILE", index_file)
    monkeypatch.setattr(models, "SESSIONS", OrderedDict(), raising=False)
    monkeypatch.setattr(models, "_active_state_db_path", lambda: state_db, raising=False)
    monkeypatch.setattr(config, "SESSION_DIR", session_dir, raising=False)
    monkeypatch.setattr(config, "SESSION_INDEX_FILE", index_file, raising=False)
    monkeypatch.setattr(streaming, "SESSION_DIR", session_dir, raising=False)
    monkeypatch.setattr(profiles, "get_active_hermes_home", lambda: tmp_path, raising=False)
    monkeypatch.setattr(routes, "SESSION_INDEX_FILE", index_file, raising=False)

    config.STREAMS.clear()
    config.CANCEL_FLAGS.clear()
    config.AGENT_INSTANCES.clear()
    config.SESSION_AGENT_LOCKS.clear()
    streaming.STREAMS.clear()
    streaming.CANCEL_FLAGS.clear()
    streaming.AGENT_INSTANCES.clear()
    streaming.STREAM_PARTIAL_TEXT.clear()
    streaming.STREAM_REASONING_TEXT.clear()
    streaming.STREAM_LIVE_TOOL_CALLS.clear()

    return config, models, routes, streaming, state_db


def _capture_streaming_history(monkeypatch, config, streaming, session, *, stream_id: str, tmp_path: Path):
    captured: dict[str, list] = {}

    class FakeAgent:
        def __init__(self, **_kwargs):
            self.session_id = session.session_id
            self.context_compressor = None
            self.ephemeral_system_prompt = None
            self.session_prompt_tokens = 0
            self.session_completion_tokens = 0
            self.session_estimated_cost_usd = None
            self.session_cache_read_tokens = 0
            self.session_cache_write_tokens = 0
            self._last_error = None

        def run_conversation(self, **kwargs):
            history = list(kwargs.get("conversation_history") or [])
            captured["conversation_history"] = history
            return {
                "completed": True,
                "final_response": "ok",
                "messages": history + [
                    {"role": "user", "content": kwargs.get("persist_user_message", "")},
                    {"role": "assistant", "content": "ok"},
                ],
            }

        def interrupt(self, _message):
            return None

    monkeypatch.setattr(streaming, "_get_ai_agent", lambda: FakeAgent)
    monkeypatch.setattr(streaming, "resolve_model_provider", lambda *args, **kwargs: ("test-model", None, None))
    monkeypatch.setattr(streaming, "get_config", lambda: {})
    monkeypatch.setattr(config, "get_config", lambda: {})
    monkeypatch.setattr(config, "_resolve_cli_toolsets", lambda *args, **kwargs: [])

    session.active_stream_id = stream_id
    session.pending_user_message = WEBUI_FOLLOWUP
    session.pending_started_at = 10.0
    session.save(touch_updated_at=False)
    config.STREAMS[stream_id] = queue.Queue()
    try:
        streaming._run_agent_streaming(
            session_id=session.session_id,
            msg_text=WEBUI_FOLLOWUP,
            model="test-model",
            workspace=str(tmp_path),
            stream_id=stream_id,
            attachments=[],
        )
    finally:
        config.STREAMS.pop(stream_id, None)
    return captured.get("conversation_history") or []


def _history_contents(history):
    return [msg.get("content") for msg in history if isinstance(msg, dict)]


def test_first_webui_followup_receives_immediate_cli_assistant_context(monkeypatch, tmp_path):
    config, models, routes, streaming, state_db = _install_cli_continuity_env(monkeypatch, tmp_path)
    sid = "issue5270_cli_child_fresh"
    _make_cli_continuation_state_db(
        state_db,
        parent_sid="issue5270_cli_parent_fresh",
        child_sid=sid,
        workspace=str(tmp_path),
    )

    session, reason = routes._claim_or_synthesize_cli_session(sid)

    assert reason == "materialized"
    assert session is not None
    assert session.read_only is False

    session.save(touch_updated_at=False)
    models.SESSIONS[sid] = session
    history = _capture_streaming_history(
        monkeypatch,
        config,
        streaming,
        session,
        stream_id="stream-issue5270-fresh",
        tmp_path=tmp_path,
    )

    assert _history_contents(history) == [CLI_PROMPT, CLI_REPLY]


def test_already_claimed_cli_sidecar_still_sees_cli_prior_assistant_on_first_webui_turn(monkeypatch, tmp_path):
    config, models, routes, streaming, state_db = _install_cli_continuity_env(monkeypatch, tmp_path)
    sid = "issue5270_cli_child_claimed"
    _make_cli_continuation_state_db(
        state_db,
        parent_sid="issue5270_cli_parent_claimed",
        child_sid=sid,
        workspace=str(tmp_path),
    )

    stale_sidecar = models.Session(
        session_id=sid,
        title="Claimed CLI child",
        workspace=str(tmp_path),
        model="test-model",
        messages=[
            {"role": "user", "content": CLI_PROMPT, "timestamp": 1.0},
        ],
        context_messages=[
            {"role": "user", "content": CLI_PROMPT, "timestamp": 1.0},
        ],
        is_cli_session=True,
        source_tag="cli",
        raw_source="cli",
        session_source="cli",
        source_label="CLI",
        read_only=False,
    )
    stale_sidecar.save(touch_updated_at=False)
    models.SESSIONS[sid] = stale_sidecar

    session = routes._get_or_materialize_session(sid, refresh_cli_messages=True)
    history = _capture_streaming_history(
        monkeypatch,
        config,
        streaming,
        session,
        stream_id="stream-issue5270-claimed",
        tmp_path=tmp_path,
    )

    assert _history_contents(history) == [CLI_PROMPT, CLI_REPLY]


def test_chat_start_refreshes_cli_messages_before_first_webui_turn(monkeypatch, tmp_path):
    _config, models, routes, _streaming, _state_db = _install_cli_continuity_env(monkeypatch, tmp_path)

    session = models.Session(
        session_id="issue5270_cli_child_chat_start",
        title="Claimed CLI child",
        workspace=str(tmp_path),
        model="test-model",
        messages=[{"role": "user", "content": CLI_PROMPT, "timestamp": 1.0}],
        context_messages=[{"role": "user", "content": CLI_PROMPT, "timestamp": 1.0}],
        is_cli_session=True,
        source_tag="cli",
        raw_source="cli",
        session_source="cli",
        source_label="CLI",
        read_only=False,
    )

    seen: dict[str, bool] = {}

    def _fake_get_or_materialize_session(sid, *, refresh_cli_messages=False):
        seen["refresh_cli_messages"] = refresh_cli_messages
        assert sid == session.session_id
        return session

    def _fake_start_run(s, **kwargs):
        assert s is session
        assert seen["refresh_cli_messages"] is True
        return {"ok": True}

    monkeypatch.setattr(routes, "_get_or_materialize_session", _fake_get_or_materialize_session)
    monkeypatch.setattr(routes, "_session_visible_to_active_profile", lambda *args, **kwargs: True)
    monkeypatch.setattr(routes, "_resolve_chat_workspace_with_recovery", lambda *args, **kwargs: str(tmp_path))
    monkeypatch.setattr(routes, "_read_profile_model_config", lambda *args, **kwargs: (None, None, None))
    monkeypatch.setattr(
        routes,
        "_resolve_compatible_session_model_state",
        lambda *args, **kwargs: ("test-model", None, "test-model"),
    )
    monkeypatch.setattr(routes, "_start_run", _fake_start_run)
    monkeypatch.setattr(routes, "j", lambda _handler, payload, status=200: {"status": status, **payload})

    response = routes._handle_chat_start(
        None,
        {
            "session_id": session.session_id,
            "message": WEBUI_FOLLOWUP,
        },
    )

    assert seen["refresh_cli_messages"] is True
    assert response["ok"] is True
    assert response["status"] == 200


def test_regular_cli_sessions_remain_writable_after_fix(monkeypatch, tmp_path):
    _config, _models, routes, _streaming, state_db = _install_cli_continuity_env(monkeypatch, tmp_path)
    sid = "issue5270_cli_child_writable"
    _make_cli_continuation_state_db(
        state_db,
        parent_sid="issue5270_cli_parent_writable",
        child_sid=sid,
        workspace=str(tmp_path),
    )

    session, reason = routes._claim_or_synthesize_cli_session(sid)

    assert reason == "materialized"
    assert session is not None
    assert session.read_only is False
    assert session.is_cli_session is True
