from decimal import Decimal

from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.test import TestCase
from django.utils import timezone

from shop_management.models.data.account_transaction import AccountTransaction, AccountTransactionType
from shop_management.models.data.accounting import JournalEntry
from shop_management.models.data.expense import Expense, ExpenseCategory
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
from shop_management.services.expenses import post_expense, void_expense


class ExpenseLifecycleTests(TestCase):
    def setUp(self):
        self.user = get_user_model().objects.create_user(username="expense-admin", password="test")
        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, currency=self.currency, name="Cash", opening_balance=Decimal("1000")
        )
        self.category = ExpenseCategory.objects.create(shop=self.shop, name="Transport")
        self.expense = Expense.objects.create(
            shop=self.shop, category=self.category, account=self.account,
            expense_by="Driver", amount=Decimal("500"), currency=self.currency,
            exchange_rate=Decimal("1"), converted_amount=Decimal("500"),
            payment_date=timezone.now(), expense_number="BR1-EXP-TEST-000001",
        )

    def test_post_then_void_creates_traceable_account_movements(self):
        self.assertEqual(self.expense.status, Expense.Status.DRAFT)
        self.assertFalse(AccountTransaction.objects.exists())

        posted = post_expense(self.expense.pk, user=self.user)
        self.assertEqual(posted.status, Expense.Status.POSTED)
        debit = AccountTransaction.objects.get()
        self.assertEqual(debit.transaction_type, AccountTransactionType.DEBIT)
        self.assertEqual(debit.converted_amount, Decimal("500"))
        journal = JournalEntry.objects.get(source_type="EXPENSE", source_id=str(self.expense.pk))
        self.assertEqual(journal.status, JournalEntry.Status.POSTED)
        self.assertEqual(journal.lines.count(), 2)

        voided = void_expense(self.expense.pk, user=self.user, reason="Duplicate entry")
        self.assertEqual(voided.status, Expense.Status.VOID)
        journal.refresh_from_db()
        self.assertEqual(journal.status, JournalEntry.Status.VOID)
        self.assertEqual(
            list(AccountTransaction.objects.order_by("id").values_list("transaction_type", flat=True)),
            [AccountTransactionType.DEBIT, AccountTransactionType.CREDIT],
        )

    def test_expense_cannot_be_posted_twice(self):
        post_expense(self.expense.pk, user=self.user)
        with self.assertRaises(ValidationError):
            post_expense(self.expense.pk, user=self.user)
        self.assertEqual(AccountTransaction.objects.count(), 1)
