from django.core.exceptions import ValidationError
from django.db import transaction
from django.utils import timezone

from shop_management.models.data.account_transaction import AccountTransaction, AccountTransactionType
from shop_management.models.data.expense import Expense
from shop_management.services.automatic_journals import create_system_journal, expense_ledger_code, financial_ledger_code, void_system_journal
from shop_management.services.account_controls import ensure_debit_allowed


def _validate(expense):
    errors = {}
    if not expense.shop_id:
        errors["shop"] = "Branch is required."
    if not expense.account_id:
        errors["account"] = "Financial account is required."
    if not expense.currency_id:
        errors["currency"] = "Currency is required."
    if expense.amount <= 0:
        errors["amount"] = "Amount must be greater than zero."
    if expense.exchange_rate <= 0:
        errors["exchange_rate"] = "Exchange rate must be greater than zero."
    if expense.account_id and expense.shop_id and expense.account.shop_id != expense.shop_id:
        errors["account"] = "Financial account must belong to the expense branch."
    if expense.currency_id and expense.shop_id and expense.currency.shop_id != expense.shop_id:
        errors["currency"] = "Currency must belong to the expense branch."
    if expense.category_id and expense.category.shop_id and expense.category.shop_id != expense.shop_id:
        errors["category"] = "Category must belong to the expense branch."
    if errors:
        raise ValidationError(errors)


@transaction.atomic
def post_expense(expense_id, *, user):
    expense = Expense.objects.select_for_update().select_related(
        "shop", "category", "account__currency", "currency"
    ).get(pk=expense_id)
    if expense.status != Expense.Status.DRAFT:
        raise ValidationError("Only draft expenses can be posted.")
    _validate(expense)
    ensure_debit_allowed(expense.account, expense.converted_amount, label="expense")
    AccountTransaction.objects.create(
        account=expense.account,
        account_currency=expense.account.currency,
        transaction_currency=expense.currency,
        transaction_type=AccountTransactionType.DEBIT,
        amount=expense.amount,
        exchange_rate=expense.exchange_rate,
        converted_amount=expense.converted_amount,
        transaction_date=expense.payment_date.date(),
        description=f"Expense {expense.expense_number} | {expense.expense_by}",
        reference=expense.expense_number or "", source_type="EXPENSE", source_id=str(expense.pk),
        is_system_generated=True, created_by=user,
    )
    expense.status = Expense.Status.POSTED
    expense.posted_at = timezone.now()
    expense.posted_by = user
    expense.save(update_fields=["status", "posted_at", "posted_by", "updated_at"])
    create_system_journal(
        shop=expense.shop, entry_date=expense.payment_date.date(), source_type="EXPENSE", source_id=expense.pk,
        description=f"Expense {expense.expense_number}", currency=expense.currency,
        exchange_rate=expense.exchange_rate, user=user, lines=[
            {"code": expense_ledger_code(expense.category), "side": "debit", "amount": expense.amount},
            {"code": financial_ledger_code(expense.account), "side": "credit", "amount": expense.amount, "financial_account": expense.account},
        ],
    )
    return expense


@transaction.atomic
def void_expense(expense_id, *, user, reason):
    expense = Expense.objects.select_for_update().select_related(
        "account__currency", "currency"
    ).get(pk=expense_id)
    if expense.status != Expense.Status.POSTED:
        raise ValidationError("Only posted expenses can be cancelled.")
    reason = str(reason or "").strip()
    if not reason:
        raise ValidationError({"void_reason": "A void reason is required."})
    AccountTransaction.objects.create(
        account=expense.account,
        account_currency=expense.account.currency,
        transaction_currency=expense.currency,
        transaction_type=AccountTransactionType.CREDIT,
        amount=expense.amount,
        exchange_rate=expense.exchange_rate,
        converted_amount=expense.converted_amount,
        transaction_date=timezone.localdate(),
        description=f"Expense void {expense.expense_number} | {reason}",
        reference=expense.expense_number or "", source_type="EXPENSE_VOID", source_id=str(expense.pk),
        is_system_generated=True, created_by=user,
    )
    expense.status = Expense.Status.VOID
    expense.voided_at = timezone.now()
    expense.voided_by = user
    expense.void_reason = reason
    expense.save(update_fields=["status", "voided_at", "voided_by", "void_reason", "updated_at"])
    void_system_journal(shop=expense.shop, source_type="EXPENSE", source_id=expense.pk, user=user, reason=reason)
    return expense
