"""Tests for the auto-reply agent runner."""

from __future__ import annotations

import base64
import json
from types import SimpleNamespace
from unittest.mock import patch

import pytest

from b2alpha.runner import AgentRunner, IncomingMessage, decode_payload_text, parse_handler_output


def _payload(text: str) -> str:
    raw = json.dumps({"text": text}).encode()
    return base64.urlsafe_b64encode(raw).rstrip(b"=").decode()


def _record(*, recipient: str = "did:b2a:zMe", message_id: str = "m1", text: str = "hello") -> dict:
    return {
        "interaction_id": "conv1",
        "messages": [
            {
                "message_id": message_id,
                "sender_did": "did:b2a:zOther",
                "recipient_did": recipient,
                "payload": _payload(text),
                "created_at": "2026-03-19T12:00:00Z",
            },
        ],
    }


class TestHelpers:
    def test_decode_payload_text(self):
        assert decode_payload_text(_payload("hello")) == "hello"

    def test_parse_handler_output_text(self):
        assert parse_handler_output("hi there\n") == ("hi there", False)

    def test_parse_handler_output_json(self):
        assert parse_handler_output('{"reply":"done","close_interaction":true}') == ("done", True)

    def test_parse_handler_output_invalid_json_shape(self):
        with pytest.raises(ValueError, match="object"):
            parse_handler_output('["nope"]')


class TestAgentRunner:
    def test_extracts_only_unprocessed_incoming_messages(self, tmp_path):
        runner = AgentRunner(
            did="did:b2a:zMe",
            token="tok",
            handler_cmd="echo",
            state_path=tmp_path / "runner-state.json",
        )
        runner._remember_processed("m1")

        record = {
            "interaction_id": "conv1",
            "messages": [
                {
                    "message_id": "m1",
                    "sender_did": "did:b2a:zOther",
                    "recipient_did": "did:b2a:zMe",
                    "payload": _payload("old"),
                    "created_at": "2026-03-19T12:00:00Z",
                },
                {
                    "message_id": "m2",
                    "sender_did": "did:b2a:zMe",
                    "recipient_did": "did:b2a:zOther",
                    "payload": _payload("self"),
                    "created_at": "2026-03-19T12:01:00Z",
                },
                {
                    "message_id": "m3",
                    "sender_did": "did:b2a:zOther",
                    "recipient_did": "did:b2a:zMe",
                    "payload": _payload("new"),
                    "created_at": "2026-03-19T12:02:00Z",
                },
            ],
        }

        messages = runner._extract_incoming_messages(record)
        assert messages == [
            IncomingMessage(
                interaction_id="conv1",
                message_id="m3",
                sender_did="did:b2a:zOther",
                recipient_did="did:b2a:zMe",
                text="new",
                created_at="2026-03-19T12:02:00Z",
            )
        ]

    @patch("b2alpha.runner.send_message")
    @patch("b2alpha.runner.sign_message")
    @patch("b2alpha.runner.subprocess.run")
    def test_process_record_replies_and_marks_processed(self, mock_run, mock_sign, mock_send, tmp_path):
        mock_run.return_value = SimpleNamespace(returncode=0, stdout="auto reply\n", stderr="")
        mock_sign.return_value = {"message_id": "out1", "payload": "p", "signature": "s"}

        runner = AgentRunner(
            did="did:b2a:zMe",
            token="tok",
            handler_cmd="handler --mode reply",
            state_path=tmp_path / "runner-state.json",
        )

        processed = runner.process_record(_record())

        assert processed == 1
        assert "m1" in runner._processed_ids
        mock_run.assert_called_once()
        mock_send.assert_called_once_with(
            "tok",
            "did:b2a:zMe",
            "did:b2a:zOther",
            {"message_id": "out1", "payload": "p", "signature": "s"},
            interaction_id="conv1",
            close_interaction=False,
        )

    @patch("b2alpha.runner.send_message")
    @patch("b2alpha.runner.sign_message")
    @patch("b2alpha.runner.subprocess.run")
    def test_process_record_supports_close_flag(self, mock_run, mock_sign, mock_send, tmp_path):
        mock_run.return_value = SimpleNamespace(
            returncode=0,
            stdout='{"reply":"closing","close_interaction":true}',
            stderr="",
        )
        mock_sign.return_value = {"message_id": "out1", "payload": "p", "signature": "s"}

        runner = AgentRunner(
            did="did:b2a:zMe",
            token="tok",
            handler_cmd="handler",
            state_path=tmp_path / "runner-state.json",
        )

        runner.process_record(_record())

        assert mock_send.call_args.kwargs["close_interaction"] is True

    @patch("b2alpha.runner.list_interaction_records")
    def test_sweep_once_processes_recent_records(self, mock_list, tmp_path):
        runner = AgentRunner(
            did="did:b2a:zMe",
            token="tok",
            handler_cmd="echo",
            state_path=tmp_path / "runner-state.json",
        )
        seen: list[str] = []
        runner.process_record = lambda record: seen.append(record["interaction_id"]) or 1  # type: ignore[method-assign]
        mock_list.return_value = [
            {"interaction_id": "newest", "messages": []},
            {"interaction_id": "older", "messages": []},
        ]

        processed = runner.sweep_once()

        assert processed == 2
        assert seen == ["older", "newest"]
