"""durable side-effect outbox 原子性、认领、重试与幂等测试。"""

from datetime import datetime, timezone
from unittest.mock import MagicMock

import pytest

from app.application.outbox import ClaimedOutboxMessage, OutboxDispatcher
from app.application.subscription.write import CreateSubscriptionCommand


class _Staged:
    """测试用新订阅暂存结果。"""

    subscribe_id = 42
    message = "ok"
    created = True


def test_subscription_and_outbox_intent_commit_together() -> None:
    """业务行与 intent 均 stage 成功后才允许同一次 commit。"""
    calls = []
    repository = MagicMock()
    repository.stage_add.side_effect = lambda *_args: calls.append("subscription") or _Staged()
    outbox = MagicMock()
    outbox.stage.side_effect = lambda *_args: calls.append("outbox")
    unit_of_work = MagicMock()
    unit_of_work.commit.side_effect = lambda: calls.append("commit")
    command = CreateSubscriptionCommand(repository, unit_of_work, outbox=outbox)

    result = command.execute({}, {"name": "demo"}, "user")

    assert result == (42, "ok")
    assert calls == ["subscription", "outbox", "outbox", "commit"]
    intent = outbox.stage.call_args_list[0].args[0]
    assert intent.event_key == "subscribe.added:42:unknown:unknown:v1"
    assert intent.payload["subscribe_id"] == 42
    report_intent = outbox.stage.call_args_list[1].args[0]
    assert report_intent.topic == "subscribe.added.report"
    assert report_intent.event_key.endswith(":report")


def test_subscription_notification_snapshot_is_part_of_same_transaction() -> None:
    """订阅新增通知快照与事件、统计意图一起暂存，便于崩溃恢复。"""
    calls = []
    repository = MagicMock()
    repository.stage_add.side_effect = lambda *_args: calls.append("subscription") or _Staged()
    outbox = MagicMock()
    outbox.stage.side_effect = lambda *_args: calls.append("outbox")
    unit_of_work = MagicMock()
    unit_of_work.commit.side_effect = lambda: calls.append("commit")
    command = CreateSubscriptionCommand(repository, unit_of_work, outbox=outbox)

    command.execute(
        {},
        {"name": "demo"},
        "user",
        notification={"title": "订阅成功", "text": "demo"},
    )

    intents = [call.args[0] for call in outbox.stage.call_args_list]
    assert [intent.topic for intent in intents] == [
        "subscribe.added",
        "subscribe.added.notification",
        "subscribe.added.report",
    ]
    assert intents[1].payload["message"]["text"] == "demo"
    assert calls[-1] == "commit"


def test_outbox_stage_failure_rolls_back_business_transaction() -> None:
    """intent 无法持久化时订阅行不得单独提交。"""
    repository = MagicMock()
    repository.stage_add.return_value = _Staged()
    outbox = MagicMock()
    outbox.stage.side_effect = RuntimeError("outbox unavailable")
    unit_of_work = MagicMock()
    command = CreateSubscriptionCommand(repository, unit_of_work, outbox=outbox)

    with pytest.raises(RuntimeError, match="outbox unavailable"):
        command.execute({}, {"name": "demo"})

    unit_of_work.rollback.assert_called_once_with()
    unit_of_work.commit.assert_not_called()


def test_dispatcher_retries_then_dead_letters_with_stable_key() -> None:
    """同一幂等键有限指数退避，达到上限后进入 dead letter。"""
    now = datetime(2026, 8, 21, tzinfo=timezone.utc)
    repository = MagicMock()
    repository.claim.side_effect = [
        ClaimedOutboxMessage(1, "subscribe.added:42:v1", "subscribe.added", {}, 1, 1),
        ClaimedOutboxMessage(1, "subscribe.added:42:v1", "subscribe.added", {}, 1, 2),
    ]
    handler = MagicMock(side_effect=RuntimeError("temporary"))
    failure_observer = MagicMock()
    dispatcher = OutboxDispatcher(
        repository,
        {"subscribe.added": handler},
        max_attempts=2,
        clock=lambda: now,
        failure_observer=failure_observer,
    )

    assert dispatcher.dispatch_one() is True
    assert repository.retry.call_args.kwargs["dead"] is False
    assert dispatcher.dispatch_one() is True
    assert repository.retry.call_args.kwargs["dead"] is True
    assert [call.args[0].event_key for call in handler.call_args_list] == [
        "subscribe.added:42:v1",
        "subscribe.added:42:v1",
    ]
    assert [call.args[0] for call in failure_observer.call_args_list] == [
        False,
        True,
    ]


def test_dispatcher_marks_success_and_closes_owned_resource() -> None:
    """成功 handler 收口消息，批次结束释放 Session 所有权。"""
    now = datetime(2026, 8, 21, tzinfo=timezone.utc)
    repository = MagicMock()
    message = ClaimedOutboxMessage(7, "key", "subscribe.added", {}, 1, 1)
    repository.claim.return_value = message
    close = MagicMock()
    dispatcher = OutboxDispatcher(
        repository,
        {"subscribe.added": MagicMock()},
        clock=lambda: now,
        close=close,
    )

    assert dispatcher.dispatch_one() is True
    repository.complete.assert_called_once_with(7, now)
    dispatcher.close()
    close.assert_called_once_with()
