from django.test import TestCase
from django.urls import reverse
from django.utils import timezone

from accounts.models import User
from finance.models import Account
from inventory.models import Product
from sales.models import (
    Customer,
    CustomerLedgerEntry,
    Invoice,
    InvoiceItem,
    InvoicePayment,
    PaymentAllocation,
    Sale,
)


class SalesLedgerTests(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            username="sales-admin",
            password="testpass123",
            role=User.Role.SUPER_ADMIN,
        )
        self.client.force_login(self.user)

    def test_customer_balance_uses_ledger_when_entries_exist(self):
        customer = Customer.objects.create(name="Ledger Customer")
        CustomerLedgerEntry.objects.create(
            customer=customer,
            entry_type=CustomerLedgerEntry.EntryType.INVOICE,
            reference="INV-TEST-1",
            debit=150,
            created_by=self.user,
        )
        CustomerLedgerEntry.objects.create(
            customer=customer,
            entry_type=CustomerLedgerEntry.EntryType.PAYMENT,
            reference="PAY-TEST-1",
            credit=40,
            created_by=self.user,
        )

        customer.refresh_from_db()
        self.assertEqual(customer.balance_due, 110)

    def test_invoice_create_posts_customer_ledger_debit(self):
        customer = Customer.objects.create(name="Invoice Customer")
        payload = {
            "customer": customer.pk,
            "customer_name": customer.name,
            "customer_phone": "",
            "customer_email": "",
            "customer_address": "",
            "issue_date": timezone.now().date().isoformat(),
            "due_date": "",
            "discount": "0",
            "note": "",
            "items-TOTAL_FORMS": "1",
            "items-INITIAL_FORMS": "0",
            "items-MIN_NUM_FORMS": "1",
            "items-MAX_NUM_FORMS": "1000",
            "items-0-product": "",
            "items-0-description": "Consulting Service",
            "items-0-quantity": "1",
            "items-0-unit_price": "120",
            "items-0-DELETE": "",
        }

        response = self.client.post(reverse("sales:invoice_create"), payload)
        self.assertEqual(response.status_code, 302)

        invoice = Invoice.objects.get()
        entry = CustomerLedgerEntry.objects.get(
            customer=customer,
            invoice=invoice,
            entry_type=CustomerLedgerEntry.EntryType.INVOICE,
        )
        self.assertEqual(entry.debit, invoice.total)
        self.assertEqual(entry.credit, 0)

    def test_invoice_payment_creates_allocation_and_ledger_credit(self):
        customer = Customer.objects.create(name="Payment Customer")
        account = Account.objects.create(
            name="Cash Till",
            account_type=Account.AccountType.CASH,
            balance=0,
        )
        invoice = Invoice.objects.create(
            reference="INV-1001",
            customer=customer,
            customer_name=customer.name,
            issue_date=timezone.now().date(),
            created_by=self.user,
        )
        InvoiceItem.objects.create(
            invoice=invoice,
            description="Stock Sale",
            quantity=2,
            unit_price=50,
            unit_cost=25,
        )
        # Mirror invoice issue entry for realistic ledger sequence.
        CustomerLedgerEntry.objects.create(
            customer=customer,
            entry_type=CustomerLedgerEntry.EntryType.INVOICE,
            reference=invoice.reference,
            debit=invoice.total,
            invoice=invoice,
            created_by=self.user,
        )

        payment_payload = {
            "account": account.pk,
            "amount": "40",
            "payment_method": Sale.PaymentMethod.CASH,
            "date": timezone.now().date().isoformat(),
            "note": "Part payment",
        }
        response = self.client.post(
            reverse("sales:invoice_payment", args=[invoice.pk]),
            payment_payload,
        )
        self.assertEqual(response.status_code, 302)

        payment = InvoicePayment.objects.get(invoice=invoice)
        allocation = PaymentAllocation.objects.get(payment=payment)
        self.assertEqual(allocation.amount, payment.amount)
        self.assertEqual(allocation.invoice, invoice)

        ledger_credit = CustomerLedgerEntry.objects.get(
            customer=customer,
            payment=payment,
            entry_type=CustomerLedgerEntry.EntryType.PAYMENT,
        )
        self.assertEqual(ledger_credit.credit, payment.amount)
        self.assertEqual(ledger_credit.debit, 0)

        account.refresh_from_db()
        self.assertEqual(account.balance, payment.amount)

    def test_credit_sale_and_cancel_posts_debit_then_credit(self):
        customer = Customer.objects.create(name="Credit Sale Customer")
        product = Product.objects.create(
            name="Exercise Book",
            sku="BK-001",
            stock_quantity=20,
            cost_price=3,
            selling_price=5,
        )
        payload = {
            "customer": customer.pk,
            "customer_name": "",
            "date": timezone.now().date().isoformat(),
            "payment_method": Sale.PaymentMethod.CREDIT,
            "account": "",
            "discount": "0",
            "note": "",
            "items-TOTAL_FORMS": "1",
            "items-INITIAL_FORMS": "0",
            "items-MIN_NUM_FORMS": "1",
            "items-MAX_NUM_FORMS": "1000",
            "items-0-product": product.pk,
            "items-0-quantity": "2",
            "items-0-unit_price": "5",
            "items-0-DELETE": "",
        }

        create_response = self.client.post(reverse("sales:sale_create"), payload)
        self.assertEqual(create_response.status_code, 302)
        sale = Sale.objects.get()

        debit_entry = CustomerLedgerEntry.objects.get(
            customer=customer,
            sale=sale,
            entry_type=CustomerLedgerEntry.EntryType.SALE,
        )
        self.assertEqual(debit_entry.debit, sale.total)

        cancel_response = self.client.get(reverse("sales:sale_cancel", args=[sale.pk]))
        self.assertEqual(cancel_response.status_code, 302)

        credit_entry = CustomerLedgerEntry.objects.filter(
            customer=customer,
            sale=sale,
            entry_type=CustomerLedgerEntry.EntryType.ADJUSTMENT,
        ).latest("created_at")
        self.assertEqual(credit_entry.credit, sale.total)
