"""Tests for the send-message CLI app."""

from datetime import UTC, datetime
from pathlib import Path
from unittest.mock import MagicMock, patch

from typer.testing import CliRunner

from send_message.cli import app
from session_store import SessionExport, SessionInfo, SessionMessage

# ─── Helpers ──────────────────────────────────────────────────────────────


def make_message(
    role: str = "assistant",
    content: str = "This is a sufficiently long assistant message for testing purposes.",
    node_id: str = "node-1",
    created_at: datetime | None = None,
) -> SessionMessage:
    return SessionMessage(
        tool="test",
        session_id="sess-1",
        node_id=node_id,
        parent_node_id=None,
        message_id=node_id,
        role=role,
        content=content,
        created_at=created_at or datetime(2026, 7, 14, 10, 0, 0, tzinfo=UTC),
    )


def make_session_export(messages: list[SessionMessage]) -> SessionExport:
    return SessionExport(
        tool="test",
        session_id="sess-1",
        title="Test Session",
        working_directory="/tmp/test",
        messages=messages,
    )


def make_session_info(**kwargs) -> SessionInfo:
    defaults = dict(
        tool="devin",
        session_id="sess-1",
        title="Test Session",
        message_count=5,
        last_activity=datetime(2026, 7, 14, 10, 0, 0, tzinfo=UTC),
        working_directory="/tmp/test",
    )
    defaults.update(kwargs)
    return SessionInfo(**defaults)


class MockSource:
    def __init__(self, sessions=None, exports=None, current="sess-1"):
        self._sessions = sessions or []
        self._exports = exports or {}
        self._current = current

    def tool_name(self):
        return "devin"

    def list_sessions(self, limit=20):
        return self._sessions[:limit]

    def load_session(self, session_id):
        if session_id not in self._exports:
            from session_store.errors import SessionNotFoundError

            raise SessionNotFoundError(f"Session '{session_id}' not found")
        return self._exports[session_id]

    def detect_current_session(self):
        return self._current


def make_mock_store(sessions=None, exports=None):
    """Create a mock SessionStore-like object."""
    store = MagicMock()
    store.detect_tool.return_value = "devin"
    store.detect_session.return_value = "sess-1"
    store.list_sessions.return_value = sessions if sessions is not None else [make_session_info()]

    default_export = make_session_export(
        [
            make_message(role="assistant", content="A" * 60, node_id="a1"),
            make_message(role="assistant", content="B" * 60, node_id="a2"),
        ]
    )
    store.get_session.return_value = (
        exports.get("sess-1", default_export) if exports else default_export
    )

    # select_message returns the latest message
    store.select_message.return_value = make_message(
        role="assistant", content="B" * 60, node_id="a2"
    )
    return store


# ─── Tests ────────────────────────────────────────────────────────────────


class TestVersionFlag:
    def test_version(self):
        runner = CliRunner()
        result = runner.invoke(app, ["--version"])
        assert result.exit_code == 0
        assert "send-message" in result.stdout
        assert "0.1.0" in result.stdout


class TestListSessions:
    @patch("send_message.commands.list_sessions.get_store")
    def test_list_sessions_basic(self, mock_get_store):
        sessions = [
            make_session_info(tool="devin", session_id="abc-123", title="My Session"),
            make_session_info(tool="opencode", session_id="def-456", title="Another Session"),
        ]
        store = make_mock_store(sessions=sessions)
        mock_get_store.return_value = store

        runner = CliRunner()
        result = runner.invoke(app, ["list-sessions"])
        assert result.exit_code == 0
        assert "My Session" in result.stdout
        assert "Another" in result.stdout

    @patch("send_message.commands.list_sessions.get_store")
    def test_list_sessions_empty(self, mock_get_store):
        store = make_mock_store(sessions=[])
        mock_get_store.return_value = store

        runner = CliRunner()
        result = runner.invoke(app, ["list-sessions"])
        assert result.exit_code == 0
        assert "No sessions found" in result.stdout


class TestPreview:
    @patch("send_message.commands._helpers.get_store")
    def test_preview_latest(self, mock_get_store):
        store = make_mock_store()
        mock_get_store.return_value = store

        runner = CliRunner()
        result = runner.invoke(
            app, ["--tool", "devin", "--session-id", "sess-1", "preview", "latest"]
        )
        assert result.exit_code == 0

    @patch("send_message.commands._helpers.get_store")
    def test_preview_full(self, mock_get_store):
        store = make_mock_store()
        mock_get_store.return_value = store

        runner = CliRunner()
        result = runner.invoke(
            app, ["--tool", "devin", "--session-id", "sess-1", "preview", "latest", "--full"]
        )
        assert result.exit_code == 0


class TestSend:
    @patch("send_message.commands._helpers.get_store")
    @patch("send_message.commands.send.DeliveryPipeline")
    def test_send_dry_run(self, mock_pipeline_cls, mock_get_store):
        store = make_mock_store()
        mock_get_store.return_value = store

        from message_delivery import DeliveryResult

        mock_pipeline = MagicMock()
        mock_pipeline.deliver.return_value = DeliveryResult(
            note_path=Path("/tmp/docs/notes/test.md"),
            committed=False,
            emailed=False,
            pushed=False,
        )
        mock_pipeline_cls.return_value = mock_pipeline

        runner = CliRunner()
        result = runner.invoke(
            app, ["--tool", "devin", "--session-id", "sess-1", "send", "latest", "--dry-run"]
        )
        assert result.exit_code == 0
        assert "Saved" in result.stdout
        assert "Dry run" in result.stdout

    @patch("send_message.commands._helpers.get_store")
    @patch("send_message.commands.send.DeliveryPipeline")
    def test_send_full(self, mock_pipeline_cls, mock_get_store):
        store = make_mock_store()
        mock_get_store.return_value = store

        from message_delivery import DeliveryResult

        mock_pipeline = MagicMock()
        mock_pipeline.deliver.return_value = DeliveryResult(
            note_path=Path("/tmp/docs/notes/test.md"),
            committed=True,
            emailed=True,
            pushed=True,
        )
        mock_pipeline_cls.return_value = mock_pipeline

        runner = CliRunner()
        result = runner.invoke(app, ["--tool", "devin", "--session-id", "sess-1", "send", "latest"])
        assert result.exit_code == 0
        assert "Saved" in result.stdout
        assert "Committed" in result.stdout
        assert "Emailed" in result.stdout
        assert "Pushed" in result.stdout


class TestListMessages:
    @patch("send_message.commands._helpers.get_store")
    def test_list_messages(self, mock_get_store):
        export = make_session_export(
            [
                make_message(role="assistant", content="A" * 60, node_id="a1"),
                make_message(role="assistant", content="B" * 60, node_id="a2"),
            ]
        )
        store = make_mock_store(exports={"sess-1": export})
        mock_get_store.return_value = store

        runner = CliRunner()
        result = runner.invoke(app, ["--tool", "devin", "--session-id", "sess-1", "list-messages"])
        assert result.exit_code == 0

    @patch("send_message.commands._helpers.get_store")
    def test_list_messages_all(self, mock_get_store):
        export = make_session_export(
            [
                make_message(role="user", content="user question", node_id="u1"),
                make_message(role="assistant", content="A" * 60, node_id="a1"),
            ]
        )
        store = make_mock_store(exports={"sess-1": export})
        mock_get_store.return_value = store

        runner = CliRunner()
        result = runner.invoke(
            app, ["--tool", "devin", "--session-id", "sess-1", "list-messages", "--all"]
        )
        assert result.exit_code == 0


class TestErrorHandling:
    @patch("send_message.commands._helpers.get_store")
    def test_no_active_session(self, mock_get_store):
        from session_store.errors import NoActiveSessionError

        store = MagicMock()
        store.detect_tool.side_effect = NoActiveSessionError("no sessions")
        mock_get_store.return_value = store

        runner = CliRunner()
        result = runner.invoke(app, ["preview", "latest"])
        assert result.exit_code == 1

    @patch("send_message.commands._helpers.get_store")
    def test_session_not_found(self, mock_get_store):
        from session_store.errors import SessionNotFoundError

        store = MagicMock()
        store.detect_tool.return_value = "devin"
        store.detect_session.return_value = "sess-1"
        store.get_session.side_effect = SessionNotFoundError("not found")
        mock_get_store.return_value = store

        runner = CliRunner()
        result = runner.invoke(
            app, ["--tool", "devin", "--session-id", "sess-1", "preview", "latest"]
        )
        assert result.exit_code == 1
