"""Regression coverage for archived state.db-projected sessions reappearing."""

from __future__ import annotations

import sqlite3
from unittest.mock import patch


def test_cron_rows_are_not_cli_even_with_stale_cli_flag():
    """A stale sidecar flag must not turn a cron row into an external CLI row."""
    from api.agent_sessions import is_cli_session_row

    row = {
        "session_id": "cron_job123_20260618",
        "title": "Cron Session",
        "source_tag": "cron",
        "raw_source": "cron",
        "session_source": "cron",
        "source_label": "Cron",
        "is_cli_session": True,
    }

    assert is_cli_session_row(row) is False


def test_materializing_cron_session_preserves_non_cli_identity(monkeypatch):
    """Cron materialization must not stamp the sidecar as CLI-imported."""
    import api.routes as routes

    sid = "cron_job123_20260618"
    cron_meta = {
        "session_id": sid,
        "title": "Cron Session",
        "model": "test-model",
        "source_tag": "cron",
        "raw_source": "cron",
        "session_source": "cron",
        "source_label": "Cron",
        "read_only": False,
        "profile": "default",
    }

    class FakeSession:
        def __init__(self):
            self.session_id = sid
            self.title = "Cron Session"
            self.profile = "default"
            self.model = "test-model"
            self.archived = False
            self.is_cli_session = False
            self.source_tag = None
            self.raw_source = None
            self.session_source = None
            self.source_label = None
            self.read_only = False

        def save(self, *args, **kwargs):
            pass

    def fake_import_cli_session(*args, **kwargs):
        return FakeSession()

    with (
        patch.object(routes, "get_session", side_effect=KeyError(sid)),
        patch.object(routes, "_lookup_cli_session_metadata", return_value=cron_meta),
        patch.object(
            routes,
            "get_cli_session_messages",
            return_value=[
                {"role": "user", "content": "run"},
                {"role": "assistant", "content": "done"},
            ],
        ),
        patch.object(routes, "import_cli_session", side_effect=fake_import_cli_session),
    ):
        session = routes._get_or_materialize_session(sid)

    assert session.session_source == "cron"
    assert session.source_tag == "cron"
    assert session.is_cli_session is False


def test_cron_state_projection_preserves_archived_sidecar(monkeypatch, tmp_path):
    """A hidden archived sidecar must still mark the state.db cron projection archived."""
    import api.models as models

    sid = "cron_job123_20260618"
    db_path = tmp_path / "state.db"
    with sqlite3.connect(db_path) as conn:
        conn.execute(
            """
            CREATE TABLE sessions (
                id TEXT PRIMARY KEY,
                title TEXT,
                model TEXT,
                message_count INTEGER,
                started_at REAL,
                source TEXT,
                session_source TEXT
            )
            """
        )
        conn.execute(
            """
            INSERT INTO sessions (
                id, title, model, message_count, started_at, source, session_source
            ) VALUES (?, ?, ?, ?, ?, ?, ?)
            """,
            (sid, "Cron Session", "test-model", 1, 20, "cron", "cron"),
        )

    # Write a real archived sidecar JSON under a patched SESSION_DIR so the
    # projection's sidecar read exercises the production path (the #4842 perf
    # fix stat-gates on SESSION_DIR/{sid}.json before consulting metadata, so a
    # mock of load_metadata_only without a real file would never be reached —
    # which mirrors production, where archived metadata only exists when the
    # file does).
    session_dir = tmp_path / "sessions"
    session_dir.mkdir()
    (session_dir / f"{sid}.json").write_text(
        '{"session_id": "%s", "title": "Cron Session", "created_at": 1.0,'
        ' "updated_at": 2.0, "archived": true, "messages": []}' % sid,
        encoding="utf-8",
    )
    monkeypatch.setattr(models, "SESSION_DIR", session_dir)
    monkeypatch.setattr(models, "_profile_has_user_projects", lambda *_a, **_kw: False)
    monkeypatch.setattr(models, "ensure_cron_project", lambda **_: "cron-project")
    models.clear_sidecar_metadata_cache()

    rows = models._load_cli_sessions_uncached(
        tmp_path,
        db_path,
        "default",
        source_filter="cron",
        include_claude_code=False,
    )

    assert len(rows) == 1
    assert rows[0]["session_id"] == sid
    assert rows[0]["archived"] is True


def test_webhook_state_projection_preserves_archived_sidecar(monkeypatch, tmp_path):
    """Archived webhook sidecars must not reappear as unarchived state.db rows."""
    import api.models as models

    sid = "webhook_archive_20260618"
    db_path = tmp_path / "state.db"
    with sqlite3.connect(db_path) as conn:
        conn.execute(
            """
            CREATE TABLE sessions (
                id TEXT PRIMARY KEY,
                title TEXT,
                model TEXT,
                message_count INTEGER,
                started_at REAL,
                source TEXT,
                user_id TEXT
            )
            """
        )
        conn.execute(
            """
            CREATE TABLE messages (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                session_id TEXT NOT NULL,
                role TEXT NOT NULL,
                content TEXT,
                timestamp REAL NOT NULL
            )
            """
        )
        conn.execute(
            """
            INSERT INTO sessions (
                id, title, model, message_count, started_at, source, user_id
            ) VALUES (?, ?, ?, ?, ?, ?, ?)
            """,
            (
                sid,
                None,
                "test-model",
                2,
                20,
                "webhook",
                "webhook:read-later",
            ),
        )
        conn.execute(
            """
            INSERT INTO messages (session_id, role, content, timestamp)
            VALUES (?, 'user', 'payload', 21)
            """,
            (sid,),
        )
        conn.execute(
            """
            INSERT INTO messages (session_id, role, content, timestamp)
            VALUES (?, 'assistant', 'done', 22)
            """,
            (sid,),
        )

    # Real archived sidecar JSON under a patched SESSION_DIR (see the cron test
    # above — the #4842 stat-gate requires the file to exist, matching prod).
    session_dir = tmp_path / "sessions"
    session_dir.mkdir()
    (session_dir / f"{sid}.json").write_text(
        '{"session_id": "%s", "title": "Webhook Session", "created_at": 1.0,'
        ' "updated_at": 2.0, "archived": true, "messages": []}' % sid,
        encoding="utf-8",
    )
    monkeypatch.setattr(models, "SESSION_DIR", session_dir)
    models.clear_sidecar_metadata_cache()

    rows = models._load_cli_sessions_uncached(
        tmp_path,
        db_path,
        "default",
        include_claude_code=False,
    )

    assert len(rows) == 1
    row = rows[0]
    assert row["session_id"] == sid
    assert row["title"] == "Webhook Session"
    assert row["source_tag"] == "webhook"
    assert row["raw_source"] == "webhook"
    assert row["session_source"] == "webhook"
    assert row["source_label"] == "Webhook"
    assert row["project_id"]
    assert row["is_cli_session"] is False
    assert row["archived"] is True


def test_archived_webhook_projection_reaches_sidebar_payload(monkeypatch):
    """Archived webhook projections are counted by default and fetched on demand."""
    import api.routes as routes

    sid = "webhook_archive_20260618"
    raw_webhook_row = {
        "session_id": sid,
        "title": "Webhook Session",
        "profile": "default",
        "updated_at": 22,
        "last_message_at": 22,
        "message_count": 2,
        "user_message_count": 1,
        "archived": True,
        "source_tag": "webhook",
        "raw_source": "webhook",
        "session_source": "webhook",
        "source_label": "Webhook",
        "project_id": "webhook-project",
        "is_cli_session": False,
    }

    monkeypatch.setattr(routes, "all_sessions", lambda diag=None: [])
    monkeypatch.setattr(routes, "get_cli_sessions", lambda source_filter=None, all_profiles=False: [raw_webhook_row])
    monkeypatch.setattr(routes, "_reconcile_stale_stream_state_for_session_rows", lambda _sessions: False)

    default_payload = routes._build_session_list_cache_payload(
        active_profile="default",
        all_profiles=False,
        show_cli_sessions=True,
        show_previous_messaging_sessions=False,
        show_cron_sessions=False,
    )
    assert [row for row in default_payload["sessions"] if row["session_id"] == sid] == []
    assert default_payload["archived_count"] == 1
    assert default_payload["archived_webui_count"] == 1

    payload = routes._build_session_list_cache_payload(
        active_profile="default",
        all_profiles=False,
        show_cli_sessions=True,
        show_previous_messaging_sessions=False,
        show_cron_sessions=False,
        include_archived=True,
    )

    rows = payload["sessions"]
    matching = [row for row in rows if row["session_id"] == sid]
    assert len(matching) == 1
    assert matching[0]["archived"] is True
    assert matching[0]["source_tag"] == "webhook"
    assert matching[0]["default_hidden"] is True
    assert matching[0]["is_cli_session"] is False


def test_archived_cron_sidecar_suppresses_raw_unarchived_cron_row(monkeypatch):
    """Archived cron sidecars stay out of the hot list but win on archive fetch."""
    import api.routes as routes

    sid = "cron_job123_20260618"
    archived_sidecar = {
        "session_id": sid,
        "title": "Cron Session",
        "profile": "default",
        "updated_at": 20,
        "last_message_at": 20,
        "message_count": 1,
        "user_message_count": 1,
        "archived": True,
        "source_tag": "cron",
        "raw_source": "cron",
        "session_source": "cron",
        "source_label": "Cron",
        "is_cli_session": True,
    }
    raw_cron_row = {
        "session_id": sid,
        "title": "Cron Session",
        "profile": "default",
        "updated_at": 20,
        "last_message_at": 20,
        "message_count": 1,
        "user_message_count": 1,
        "archived": False,
        "project_id": "cron-project",
        "source_tag": "cron",
        "raw_source": "cron",
        "session_source": "cron",
        "source_label": "Cron",
        "is_cli_session": False,
    }

    monkeypatch.setattr(routes, "all_sessions", lambda diag=None: [archived_sidecar])
    monkeypatch.setattr(routes, "get_cli_sessions", lambda source_filter=None, all_profiles=False: [raw_cron_row])
    monkeypatch.setattr(routes, "_reconcile_stale_stream_state_for_session_rows", lambda _sessions: False)

    default_payload = routes._build_session_list_cache_payload(
        active_profile="default",
        all_profiles=False,
        show_cli_sessions=True,
        show_previous_messaging_sessions=False,
        show_cron_sessions=True,
    )
    assert [row for row in default_payload["sessions"] if row["session_id"] == sid] == []
    assert default_payload["archived_count"] == 1
    assert default_payload["archived_webui_count"] == 1
    assert default_payload["archived_cli_count"] == 0

    payload = routes._build_session_list_cache_payload(
        active_profile="default",
        all_profiles=False,
        show_cli_sessions=True,
        show_previous_messaging_sessions=False,
        show_cron_sessions=True,
        include_archived=True,
    )

    rows = payload["sessions"]
    matching = [row for row in rows if row["session_id"] == sid]
    assert len(matching) == 1
    assert matching[0]["archived"] is True
    assert matching[0]["is_cli_session"] is False
