from decimal import Decimal

from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.test import TestCase

from shop_management.models.data.customer import Customer
from shop_management.models.data.opening_balance import FinancialOpeningBalance
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.utils import Currency
from shop_management.services.opening_balances import post_financial_opening, void_financial_opening


class FinancialOpeningBalanceTests(TestCase):
    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(username="accountant", password="x", is_staff=True)
        self.shop = Shop.objects.create(owner=self.user, name="Kabul")
        self.currency = Currency.objects.create(shop=self.shop, title="AFN")
        self.customer = Customer.objects.create(shop=self.shop, name="Customer")
        self.supplier = Supplier.objects.create(shop=self.shop, name="Supplier")
        self.account = Account.objects.create(shop=self.shop, name="Main Cash", account_type="CASH", currency=self.currency)

    def opening(self, kind, **target):
        return FinancialOpeningBalance.objects.create(
            shop=self.shop, balance_type=kind, currency=self.currency,
            amount=Decimal("1000"), created_by=self.user, **target,
        )

    def test_customer_opening_posts_balanced_journal(self):
        document = post_financial_opening(self.opening("CUSTOMER", customer=self.customer).pk, user=self.user)
        self.customer.refresh_from_db()
        self.assertEqual(self.customer.opening_balance, Decimal("1000"))
        self.assertEqual(document.status, "POSTED")
        self.assertEqual(document.journal_entry.status, "POSTED")
        lines = {line.ledger_account.code: (line.debit, line.credit) for line in document.journal_entry.lines.all()}
        self.assertEqual(lines["1400"], (Decimal("1000"), Decimal("0")))
        self.assertEqual(lines["3100"], (Decimal("0"), Decimal("1000")))

    def test_supplier_and_cash_use_correct_control_accounts(self):
        supplier_doc = post_financial_opening(self.opening("SUPPLIER", supplier=self.supplier).pk, user=self.user)
        account_doc = post_financial_opening(self.opening("ACCOUNT", financial_account=self.account).pk, user=self.user)
        self.assertEqual(set(supplier_doc.journal_entry.lines.values_list("ledger_account__code", flat=True)), {"2100", "3100"})
        self.assertEqual(set(account_doc.journal_entry.lines.values_list("ledger_account__code", flat=True)), {"1100", "3100"})

    def test_negative_account_opening_records_liability_and_balanced_journal(self):
        document = self.opening("ACCOUNT", financial_account=self.account)
        document.amount = Decimal("-750")
        document.save(update_fields=["amount"])

        document = post_financial_opening(document.pk, user=self.user)
        self.account.refresh_from_db()
        lines = {line.ledger_account.code: (line.debit, line.credit) for line in document.journal_entry.lines.all()}

        self.assertEqual(self.account.opening_balance, Decimal("-750"))
        self.assertEqual(lines["1100"], (Decimal("0"), Decimal("750")))
        self.assertEqual(lines["3100"], (Decimal("750"), Decimal("0")))

    def test_negative_customer_or_supplier_opening_is_rejected(self):
        for kind, target in (("CUSTOMER", {"customer": self.customer}), ("SUPPLIER", {"supplier": self.supplier})):
            document = self.opening(kind, **target)
            document.amount = Decimal("-100")
            with self.assertRaises(ValidationError):
                document.full_clean()

    def test_duplicate_or_legacy_opening_is_rejected(self):
        self.customer.opening_balance = 50
        self.customer.opening_balance_currency = self.currency
        self.customer.save()
        with self.assertRaises(ValidationError):
            post_financial_opening(self.opening("CUSTOMER", customer=self.customer).pk, user=self.user)

    def test_void_clears_projection_and_voids_journal(self):
        document = post_financial_opening(self.opening("ACCOUNT", financial_account=self.account).pk, user=self.user)
        document = void_financial_opening(document.pk, user=self.user, reason="Incorrect go-live count")
        self.account.refresh_from_db()
        document.journal_entry.refresh_from_db()
        self.assertEqual(self.account.opening_balance, Decimal("0"))
        self.assertEqual(document.status, "VOID")
        self.assertEqual(document.journal_entry.status, "VOID")
