from io import StringIO
from unittest.mock import patch

from django.core.management import call_command
from django.test import TestCase

from orders.models import Order
from webhooks.models import ShopifyRefundEvent


class AuditRefundMismatchesCommandTests(TestCase):
    @patch("products.services.digistore_service.DigistoreService.get_purchase")
    def test_lists_mismatch_for_refunded_without_refund_transaction(self, get_purchase_mock):
        get_purchase_mock.return_value = {
            "is_refundable_now": "N",
            "last_refund_possible_at": "2026-05-12",
            "transaction_list": [{"type": "payment", "amount": "10.00"}],
        }
        Order.objects.create(
            shopify_order_id="shop-a",
            digistore_order_id="DS-A",
            status="refunded",
        )

        out = StringIO()
        call_command("audit_refund_mismatches", stdout=out)
        content = out.getvalue()

        self.assertIn("Mismatches found: 1", content)
        self.assertIn("DS-A", content)
        self.assertIn("no_refund_transaction_in_digistore", content)

    @patch("orders.services.order_service.OrderService.refund_purchase")
    @patch("products.services.digistore_service.DigistoreService.get_purchase")
    def test_retry_full_force_dry_run_does_not_call_refund_api(
        self,
        get_purchase_mock,
        refund_purchase_mock,
    ):
        get_purchase_mock.return_value = {
            "is_refundable_now": "N",
            "last_refund_possible_at": "2026-05-12",
            "transaction_list": [{"type": "payment", "amount": "37.49"}],
        }
        Order.objects.create(
            shopify_order_id="shop-b",
            digistore_order_id="DS-B",
            status="refunded",
        )
        out = StringIO()
        call_command(
            "audit_refund_mismatches",
            retry_full_force=True,
            dry_run=True,
            stdout=out,
        )

        refund_purchase_mock.assert_not_called()
        self.assertIn("Retries attempted: 1", out.getvalue())
        self.assertIn("retry=dry_run", out.getvalue())

    @patch("orders.services.order_service.OrderService.refund_purchase")
    @patch("products.services.digistore_service.DigistoreService.get_purchase")
    def test_retry_full_force_calls_refund_api_for_full_only(
        self,
        get_purchase_mock,
        refund_purchase_mock,
    ):
        get_purchase_mock.return_value = {
            "is_refundable_now": "N",
            "last_refund_possible_at": "2026-05-12",
            "transaction_list": [{"type": "payment", "amount": "37.49"}],
        }
        refund_purchase_mock.return_value = {"result": "success"}
        Order.objects.create(
            shopify_order_id="shop-c",
            digistore_order_id="DS-C",
            status="refunded",
        )
        Order.objects.create(
            shopify_order_id="shop-d",
            digistore_order_id="DS-D",
            status="partially refunded",
        )

        call_command("audit_refund_mismatches", retry_full_force=True)

        refund_purchase_mock.assert_called_once()
        kwargs = refund_purchase_mock.call_args.kwargs
        self.assertTrue(kwargs.get("force"))

    @patch("products.services.digistore_service.DigistoreService.get_purchase")
    def test_partial_with_negative_refund_transaction_is_not_mismatch(self, get_purchase_mock):
        get_purchase_mock.return_value = {
            "is_refundable_now": "N",
            "last_refund_possible_at": "2026-05-12",
            "transaction_list": [
                {"type": "payment", "amount": "42.48"},
                {"type": "refund", "amount": "-37.49"},
            ],
        }
        Order.objects.create(
            shopify_order_id="shop-neg",
            digistore_order_id="DS-NEG",
            status="partially refunded",
        )

        out = StringIO()
        call_command("audit_refund_mismatches", stdout=out)
        content = out.getvalue()
        self.assertIn("Mismatches found: 0", content)

    @patch("orders.services.order_service.OrderService.refund_purchase")
    @patch("products.services.digistore_service.DigistoreService.get_purchase")
    def test_failed_retry_marks_latest_event_processed_false_with_reason(
        self,
        get_purchase_mock,
        refund_purchase_mock,
    ):
        get_purchase_mock.return_value = {
            "is_refundable_now": "N",
            "last_refund_possible_at": "2026-05-12",
            "transaction_list": [{"type": "payment", "amount": "10.00"}],
        }
        refund_purchase_mock.return_value = {
            "result": "success",
            "data": {"status": "failure", "status_msg": "No refundable payment"},
        }
        Order.objects.create(
            shopify_order_id="shop-e",
            digistore_order_id="DS-E",
            status="refunded",
        )
        event = ShopifyRefundEvent.objects.create(
            webhook_id="wh-e",
            shopify_order_id="shop-e",
            digistore_order_id="DS-E",
            payload={"id": "r-e"},
            processed=True,
            error="",
        )

        call_command("audit_refund_mismatches", retry_full_force=True)

        event.refresh_from_db()
        self.assertFalse(event.processed)
        self.assertIn("manual_retry_failed", event.error)
        self.assertIn("status=failure", event.error)
