from datetime import timedelta
from decimal import Decimal

from django.contrib.auth import get_user_model
from django.test import TestCase
from django.utils import timezone

from shop_management.models.data.customer import Customer
from shop_management.models.data.customer_balance_payment import CustomerBalancePayment
from shop_management.models.data.customer_payment import CustomerPayment
from shop_management.models.data.inventory import InventoryBatch
from shop_management.models.data.organization import Warehouse
from shop_management.models.data.product import Product
from shop_management.models.data.sale import Sale, SaleItem
from shop_management.models.data.shop import Shop
from shop_management.models.data.shop_account import Account
from shop_management.models.data.utils import Currency, ExchangeRate, Unit
from shop_management.services.inventory import receive_stock
from shop_management.services.sales import post_sale, void_sale
from shop_management.views.data.sale import SaleViewSet


class CustomerCreditApplicationTests(TestCase):
    def setUp(self):
        self.user = get_user_model().objects.create_user(
            username="credit-seller", password="test", is_staff=True
        )
        self.shop = Shop.objects.create(owner=self.user, name="Kabul")
        self.warehouse = Warehouse.objects.create(
            shop=self.shop, code="MAIN", name="Main"
        )
        self.afn = Currency.objects.create(shop=self.shop, title="AFN")
        self.afn_account = Account.objects.create(
            shop=self.shop, name="AFN Cash", currency=self.afn
        )
        self.customer = Customer.objects.create(
            shop=self.shop, name="Credit Customer"
        )
        self.unit = Unit.objects.create(shop=self.shop, title="Each")
        self.product = Product.objects.create(
            shop=self.shop,
            name="Medicine",
            default_unit=self.unit,
            requires_batch=True,
            requires_expiry=True,
        )
        self.batch = InventoryBatch.objects.create(
            product=self.product,
            batch_number="CREDIT",
            expiry_date=timezone.localdate() + timedelta(days=90),
        )
        receive_stock(
            warehouse=self.warehouse,
            product=self.product,
            batch=self.batch,
            quantity=10,
            unit_cost=10,
            currency=self.afn,
        )

    def create_credit(self, amount, currency, account):
        return CustomerBalancePayment.objects.create(
            customer=self.customer,
            account=account,
            amount=amount,
            currency=currency,
            settlement_currency=currency,
            settlement_amount=amount,
            unallocated_amount=amount,
        )

    def create_draft_sale(self, total):
        sale = Sale.objects.create(
            shop=self.shop,
            warehouse=self.warehouse,
            currency=self.afn,
            customer=self.customer,
            sales_type=Sale.SalesType.CREDIT,
            invoice_date=timezone.localdate(),
            discount_type=Sale.DiscountType.FIXED,
            discount_value=0,
            received_amount=0,
            uses_inventory_engine=True,
            document_status=Sale.DocumentStatus.DRAFT,
        )
        SaleItem.objects.create(
            sale=sale,
            product=self.product,
            unit=self.unit,
            quantity=1,
            sale_price=total,
        )
        return sale

    def test_posting_sale_uses_existing_same_currency_credit_first(self):
        self.create_credit(Decimal("20.00"), self.afn, self.afn_account)
        sale = self.create_draft_sale(Decimal("27.00"))

        post_sale(sale.id, user=self.user)

        sale.refresh_from_db()
        self.assertEqual(sale.total_remained_amount["AFN"], Decimal("7.00"))
        self.assertEqual(self.customer.get_credit_by_currency(), {})
        self.assertEqual(self.customer.get_total_owed_by_currency(), {"AFN": 7.0})

    def test_posting_sale_converts_credit_before_creating_due(self):
        usd = Currency.objects.create(shop=self.shop, title="USD")
        usd_account = Account.objects.create(
            shop=self.shop, name="USD Cash", currency=usd
        )
        ExchangeRate.objects.create(
            shop=self.shop,
            from_currency=usd,
            to_currency=self.afn,
            rate=Decimal("63.400000"),
            rate_date=timezone.localdate(),
        )
        self.create_credit(Decimal("95.68"), usd, usd_account)
        sale = self.create_draft_sale(Decimal("9500.00"))

        post_sale(sale.id, user=self.user)

        sale.refresh_from_db()
        application = sale.credit_applications.get()
        self.assertEqual(application.source_amount, Decimal("95.68"))
        self.assertEqual(application.sale_amount, Decimal("6066.11"))
        self.assertEqual(application.exchange_rate, Decimal("63.400000"))
        self.assertEqual(sale.total_remained_amount["AFN"], Decimal("3433.89"))
        self.assertEqual(self.customer.get_credit_by_currency(), {})
        self.assertEqual(
            self.customer.get_total_owed_by_currency(), {"AFN": 3433.89}
        )

    def test_voiding_sale_restores_credit_applied_when_posted(self):
        self.create_credit(Decimal("20.00"), self.afn, self.afn_account)
        sale = self.create_draft_sale(Decimal("27.00"))
        post_sale(sale.id, user=self.user)
        self.assertEqual(self.customer.get_credit_by_currency(), {})

        void_sale(sale.id, user=self.user, reason="Customer cancelled")

        self.assertEqual(self.customer.get_credit_by_currency(), {"AFN": 20.0})
        self.assertEqual(self.customer.get_total_owed_by_currency(), {})

    def test_cash_received_after_credit_only_allocates_remaining_due(self):
        self.create_credit(Decimal("20.00"), self.afn, self.afn_account)
        sale = self.create_draft_sale(Decimal("27.00"))

        SaleViewSet()._complete_engine_sale(
            sale,
            self.user,
            financial_account_id=self.afn_account.id,
            received_amount=Decimal("10.00"),
        )

        sale.refresh_from_db()
        payment = CustomerPayment.objects.get(allocations__sale=sale)
        allocation = payment.allocations.get(sale=sale)
        self.assertEqual(allocation.allocated_amount, Decimal("7.00"))
        self.assertEqual(payment.unallocated_amount, Decimal("3.00"))
        self.assertEqual(sale.total_remained_amount["AFN"], Decimal("0.00"))
        self.assertEqual(self.customer.get_credit_by_currency(), {"AFN": 3.0})
    def test_new_sale_applies_credit_to_oldest_unpaid_invoice_first(self):
        older_sale = self.create_draft_sale(Decimal("9500.00"))
        post_sale(older_sale.id, user=self.user)

        usd = Currency.objects.create(shop=self.shop, title="USD")
        usd_account = Account.objects.create(
            shop=self.shop, name="USD Cash", currency=usd
        )
        ExchangeRate.objects.create(
            shop=self.shop,
            from_currency=usd,
            to_currency=self.afn,
            rate=Decimal("63.400000"),
            rate_date=timezone.localdate(),
        )
        self.create_credit(Decimal("95.68"), usd, usd_account)
        newer_sale = self.create_draft_sale(Decimal("180.00"))

        post_sale(newer_sale.id, user=self.user)

        older_sale.refresh_from_db()
        newer_sale.refresh_from_db()
        self.assertEqual(
            older_sale.total_remained_amount["AFN"], Decimal("3433.89")
        )
        self.assertEqual(
            newer_sale.total_remained_amount["AFN"], Decimal("180.00")
        )
        self.assertEqual(self.customer.get_credit_by_currency(), {})
        self.assertEqual(
            self.customer.get_total_owed_by_currency(), {"AFN": 3613.89}
        )