from decimal import Decimal

from django.contrib.auth import get_user_model
from django.test import TestCase
from django.utils import timezone
from rest_framework.test import APIClient

from shop_management.models.data.account_transaction import AccountTransaction
from shop_management.models.data.purchase import Purchase, PurchaseItem
from shop_management.models.data.shop import Shop
from shop_management.models.data.shop_account import Account
from shop_management.models.data.supplier import Supplier
from shop_management.models.data.supplier_payment import SupplierPayment
from shop_management.models.data.product import Product
from shop_management.models.data.utils import Currency, Unit


class SupplierPaymentTests(TestCase):
    def setUp(self):
        self.user = get_user_model().objects.create_user(username="payer", password="test", is_staff=True)
        self.user.allowed_tabs = ["purchase_payments"]
        self.user.save(update_fields=["allowed_tabs"])
        self.shop = Shop.objects.create(owner=self.user, name="Kabul")
        self.currency = Currency.objects.create(shop=self.shop, title="AFN")
        self.account = Account.objects.create(shop=self.shop, name="Cash", currency=self.currency, opening_balance=1000)
        self.supplier = Supplier.objects.create(shop=self.shop, name="Vendor")
        self.unit = Unit.objects.create(shop=self.shop, title="Each")
        self.product = Product.objects.create(shop=self.shop, name="Item")
        self.purchases = []
        for amount in ("100", "150"):
            purchase = Purchase.objects.create(
                shop=self.shop, supplier=self.supplier, currency=self.currency,
                document_status=Purchase.DocumentStatus.POSTED, uses_inventory_engine=True,
            )
            PurchaseItem.objects.create(purchase=purchase, product=self.product, unit=self.unit, quantity=1, buy_price=amount)
            self.purchases.append(purchase)
        self.client = APIClient()
        self.client.force_authenticate(self.user)

    def create_payment(self, amount="300", allocations=None, service_charge="0", notes=""):
        return self.client.post("/api/shop/supplier-payments/", {
            "supplier": self.supplier.id, "shop": self.shop.id,
            "financial_account": self.account.id, "currency": self.currency.id,
            "amount": amount, "exchange_rate_used": "1", "payment_date": str(timezone.localdate()),
            "service_charge": service_charge,
            "reference": "PAY-1", "notes": notes, "allocations": allocations or [],
        }, format="json")

    def test_multi_invoice_allocation_and_on_account_amount(self):
        response = self.create_payment(allocations=[
            {"purchase": self.purchases[0].id, "allocated_amount": "100"},
            {"purchase": self.purchases[1].id, "allocated_amount": "120"},
        ])
        self.assertEqual(response.status_code, 201, response.data)
        payment = SupplierPayment.objects.get(pk=response.data["id"])
        self.assertEqual(payment.status, "DRAFT")
        self.assertEqual(payment.unallocated_amount, Decimal("80.00"))
        posted = self.client.post(f"/api/shop/supplier-payments/{payment.id}/post/")
        self.assertEqual(posted.status_code, 200, posted.data)
        self.assertEqual(AccountTransaction.objects.filter(transaction_type="debit").count(), 1)
        self.assertEqual(self.purchases[0].total_paid["AFN"], Decimal("100.00"))

    def test_overallocation_is_atomic(self):
        response = self.create_payment(amount="50", allocations=[
            {"purchase": self.purchases[0].id, "allocated_amount": "60"}
        ])
        payment = SupplierPayment.objects.get(pk=response.data["id"])
        posted = self.client.post(f"/api/shop/supplier-payments/{payment.id}/post/")
        self.assertEqual(posted.status_code, 400)
        payment.refresh_from_db()
        self.assertEqual(payment.status, "DRAFT")
        self.assertEqual(AccountTransaction.objects.count(), 0)

    def test_void_creates_cash_reversal_and_removes_allocation_from_paid_total(self):
        response = self.create_payment(amount="100", allocations=[
            {"purchase": self.purchases[0].id, "allocated_amount": "100"}
        ])
        payment_id = response.data["id"]
        self.client.post(f"/api/shop/supplier-payments/{payment_id}/post/")
        missing_reason = self.client.post(f"/api/shop/supplier-payments/{payment_id}/void/", {}, format="json")
        self.assertEqual(missing_reason.status_code, 400)
        voided = self.client.post(f"/api/shop/supplier-payments/{payment_id}/void/", {"reason": "Wrong account"}, format="json")
        self.assertEqual(voided.status_code, 200, voided.data)
        self.assertEqual(AccountTransaction.objects.filter(transaction_type="credit").count(), 1)
        self.assertEqual(self.purchases[0].total_paid.get("AFN", Decimal("0")), Decimal("0"))

    def test_sarafi_service_charge_is_separate_from_supplier_allocation(self):
        self.account.account_type = "SARAFI"
        self.account.sarafi_name = "Ahmad"
        self.account.save(update_fields=["account_type", "sarafi_name"])
        response = self.create_payment(amount="100", service_charge="5", notes="Sarafi transfer fee", allocations=[
            {"purchase": self.purchases[0].id, "allocated_amount": "100"}
        ])
        payment = SupplierPayment.objects.get(pk=response.data["id"])
        self.assertEqual(self.client.post(f"/api/shop/supplier-payments/{payment.id}/post/").status_code, 200)
        self.assertEqual(list(AccountTransaction.objects.order_by("id").values_list("amount", flat=True)), [Decimal("100"), Decimal("5")])
        self.assertEqual(self.purchases[0].total_paid["AFN"], Decimal("100"))

    def test_service_charge_requires_explanation(self):
        response = self.create_payment(amount="100", service_charge="5", allocations=[
            {"purchase": self.purchases[0].id, "allocated_amount": "100"}
        ])
        self.assertEqual(response.status_code, 400)
        self.assertIn("notes", response.data)
