"""
Tests for django_stripe_billing.tasks.

Covers process_webhook_event_sync and (when Celery absent) process_stripe_webhook_event.
"""
import json
import pytest
from unittest.mock import patch

from django_stripe_billing.models import StripeWebhookEvent
from django_stripe_billing.tasks import process_webhook_event_sync, process_stripe_webhook_event
from django_stripe_billing.webhooks import TransientWebhookError, registry


@pytest.mark.django_db
class TestProcessWebhookEventSync:
    """process_webhook_event_sync loads event, dispatches handler, updates status."""

    def test_skips_when_event_not_found(self):
        process_webhook_event_sync(99999)
        # No exception; logs error

    def test_skips_when_already_success(self):
        ev = StripeWebhookEvent.objects.create(
            stripe_event_id="evt_already",
            event_type="invoice.payment_succeeded",
            payload='{"data": {"object": {}}}',
            status="success",
        )
        process_webhook_event_sync(ev.pk)
        ev.refresh_from_db()
        assert ev.status == "success"

    def test_marks_skipped_when_no_handler_registered(self):
        ev = StripeWebhookEvent.objects.create(
            stripe_event_id="evt_nohandler",
            event_type="unknown.event.type",
            payload='{"data": {"object": {"id": "obj_1"}}}',
        )
        process_webhook_event_sync(ev.pk)
        ev.refresh_from_db()
        assert ev.status == "skipped"
        assert "No handler" in ev.error_message

    def test_marks_error_on_invalid_json_payload(self):
        ev = StripeWebhookEvent.objects.create(
            stripe_event_id="evt_badjson",
            event_type="invoice.payment_succeeded",
            payload="not valid json",
        )
        process_webhook_event_sync(ev.pk)
        ev.refresh_from_db()
        assert ev.status == "error"
        assert "Payload parse" in ev.error_message or "JSON" in ev.error_message

    def test_calls_handler_and_marks_success(self):
        ev = StripeWebhookEvent.objects.create(
            stripe_event_id="evt_ok",
            event_type="invoice.payment_succeeded",
            payload=json.dumps({
                "data": {"object": {"customer": "cus_123", "amount_paid": 1000}},
            }),
        )
        with patch("django_stripe_billing.utils.requests.post"):
            process_webhook_event_sync(ev.pk)
        ev.refresh_from_db()
        assert ev.status == "success"
        assert ev.processed_at is not None

    def test_raises_transient_webhook_error_when_handler_raises_it(self):
        ev = StripeWebhookEvent.objects.create(
            stripe_event_id="evt_transient",
            event_type="invoice.payment_succeeded",
            payload=json.dumps({"data": {"object": {"customer": "cus_1"}}}),
        )
        def failing_handler(_data):
            raise TransientWebhookError("temporary")
        with patch.dict(
            registry._handlers,
            {"invoice.payment_succeeded": failing_handler},
        ):
            with pytest.raises(TransientWebhookError):
                process_webhook_event_sync(ev.pk)
        ev.refresh_from_db()
        # Should not be marked success or permanent error; task will retry
        assert ev.status == "processing"

    def test_marks_error_on_handler_exception(self):
        ev = StripeWebhookEvent.objects.create(
            stripe_event_id="evt_permerr",
            event_type="invoice.payment_succeeded",
            payload=json.dumps({"data": {"object": {}}}),
        )
        def broken_handler(_data):
            raise ValueError("business rule violated")
        with patch.dict(
            registry._handlers,
            {"invoice.payment_succeeded": broken_handler},
        ):
            process_webhook_event_sync(ev.pk)
        ev.refresh_from_db()
        assert ev.status == "error"
        assert "business rule" in ev.error_message or "ValueError" in ev.error_message


@pytest.mark.django_db
class TestProcessStripeWebhookEventTask:
    """process_stripe_webhook_event wraps sync; when Celery missing, no .delay."""

    def test_sync_function_handles_nonexistent_id_without_raising(self):
        # process_webhook_event_sync is the core; it should not raise when event id missing.
        process_webhook_event_sync(99998)
