import asyncio
import json
from types import SimpleNamespace
from unittest.mock import AsyncMock

from bot.payment_providers import cryptopay, severpay, stars


class _FakeSession:
    def __call__(self):
        return self

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc, tb):
        return False

    async def commit(self):
        pass

    async def rollback(self):
        pass


class _FakeJsonRequest:
    def __init__(self, payload):
        self._payload = payload

    async def json(self):
        return self._payload


def test_cryptopay_duplicate_success_webhook_does_not_finalize_again(monkeypatch):
    session = _FakeSession()
    payment = SimpleNamespace(payment_id=77, status="succeeded")
    update = SimpleNamespace(
        payload=SimpleNamespace(
            payload=json.dumps(
                {
                    "user_id": "42",
                    "subscription_months": "0",
                    "payment_db_id": "77",
                    "sale_mode": "hwid_devices@standard",
                }
            ),
            invoice_id=9001,
            amount=100,
            asset="USDT",
        )
    )
    app = {
        "async_session_factory": session,
        "bot": SimpleNamespace(),
        "settings": SimpleNamespace(traffic_sale_mode=False, DEFAULT_CURRENCY_SYMBOL="RUB"),
        "i18n": SimpleNamespace(),
        "subscription_service": SimpleNamespace(),
        "referral_service": SimpleNamespace(),
    }

    monkeypatch.setattr(
        cryptopay.payment_dal,
        "get_payment_by_db_id",
        AsyncMock(return_value=payment),
    )
    monkeypatch.setattr(
        cryptopay.payment_dal,
        "update_provider_payment_and_status",
        AsyncMock(side_effect=AssertionError("duplicate webhook must not update payment")),
    )
    monkeypatch.setattr(
        cryptopay,
        "finalize_successful_payment",
        AsyncMock(side_effect=AssertionError("duplicate webhook must not finalize")),
    )

    service = SimpleNamespace(settings=SimpleNamespace(traffic_sale_mode=False))
    asyncio.run(cryptopay.CryptoPayService._invoice_paid_handler(service, update, app))


def test_severpay_duplicate_success_webhook_does_not_finalize_again(monkeypatch):
    session = _FakeSession()
    payment = SimpleNamespace(
        payment_id=88,
        user_id=42,
        status="succeeded",
        sale_mode="hwid_devices@standard",
        purchased_hwid_devices=3,
        purchased_gb=None,
        subscription_duration_months=None,
        amount=150.0,
        currency="RUB",
        user=None,
    )

    async def lookup_payment(_session, *, order_id_raw=None, provider_payment_id=None):
        assert _session is session
        assert order_id_raw == "88"
        assert provider_payment_id == "sev-1"
        return payment

    monkeypatch.setattr(severpay, "lookup_payment_by_order_or_provider_id", lookup_payment)
    monkeypatch.setattr(
        severpay.payment_dal,
        "update_provider_payment_and_status",
        AsyncMock(side_effect=AssertionError("duplicate webhook must not update payment")),
    )
    monkeypatch.setattr(
        severpay,
        "finalize_successful_payment",
        AsyncMock(side_effect=AssertionError("duplicate webhook must not finalize")),
    )

    service = SimpleNamespace(
        configured=True,
        _validate_signature=lambda _payload: True,
        async_session_factory=session,
        settings=SimpleNamespace(traffic_sale_mode=False),
        bot=SimpleNamespace(),
        i18n=SimpleNamespace(),
        subscription_service=SimpleNamespace(),
        referral_service=SimpleNamespace(),
    )
    response = asyncio.run(
        severpay.SeverPayService.webhook_route(
            service,
            _FakeJsonRequest(
                {
                    "type": "payin",
                    "data": {
                        "id": "sev-1",
                        "order_id": "88",
                        "status": "success",
                    },
                }
            ),
        )
    )

    assert response.status == 200


def test_stars_duplicate_success_message_does_not_finalize_again(monkeypatch):
    session = _FakeSession()
    payment = SimpleNamespace(payment_id=99, status="succeeded")
    message = SimpleNamespace(
        successful_payment=SimpleNamespace(provider_payment_charge_id="stars-charge-1"),
        from_user=SimpleNamespace(id=42),
    )

    monkeypatch.setattr(
        stars.payment_dal,
        "get_payment_by_db_id",
        AsyncMock(return_value=payment),
    )
    monkeypatch.setattr(
        stars.payment_dal,
        "update_provider_payment_and_status",
        AsyncMock(side_effect=AssertionError("duplicate stars payment must not update")),
    )
    monkeypatch.setattr(
        stars,
        "finalize_successful_payment",
        AsyncMock(side_effect=AssertionError("duplicate stars payment must not finalize")),
    )

    service = SimpleNamespace()
    asyncio.run(
        stars.StarsService.process_successful_payment(
            service,
            session=session,
            message=message,
            payment_db_id=99,
            months=3,
            stars_amount=150,
            i18n_data={},
            sale_mode="hwid_devices@standard",
        )
    )
