from decimal import Decimal

from django.core.exceptions import ValidationError
from django.db import transaction
from django.utils import timezone

from shop_management.models.data.opening_balance import FinancialOpeningBalance
from shop_management.models.data.utils import classify_currency_code, resolve_shop_currency
from shop_management.services.automatic_journals import create_system_journal, financial_ledger_code, void_system_journal


def _number(document):
    prefix = f"BR{document.shop_id}-OPN-{document.opening_date.year}-"
    last = FinancialOpeningBalance.objects.filter(opening_number__startswith=prefix).order_by("-opening_number").values_list("opening_number", flat=True).first()
    return f"{prefix}{(int(last.rsplit('-', 1)[-1]) + 1 if last else 1):06d}"


def _target(document):
    return document.customer or document.supplier or document.financial_account


def _existing_party_opening_for_currency(party, currency):
    code = classify_currency_code(currency)
    if code == "AFN":
        return Decimal(str(party.opening_balance_afn or 0))
    if code == "USD":
        return Decimal(str(party.opening_balance_usd or 0))
    if party.opening_balance_currency_id == currency.id:
        return Decimal(str(party.opening_balance or 0))
    return Decimal("0")


@transaction.atomic
def post_financial_opening(document_id, *, user):
    document = FinancialOpeningBalance.objects.select_for_update().select_related("shop", "currency", "customer", "supplier", "financial_account").get(pk=document_id)
    if document.status != document.Status.DRAFT:
        raise ValidationError("Only draft opening balances can be posted.")
    document.full_clean()
    target = _target(document)
    target = target.__class__.objects.select_for_update().get(pk=target.pk)
    if document.balance_type == document.BalanceType.CUSTOMER:
        document.customer = target
    elif document.balance_type == document.BalanceType.SUPPLIER:
        document.supplier = target
    else:
        document.financial_account = target
    duplicate = FinancialOpeningBalance.objects.filter(status=FinancialOpeningBalance.Status.POSTED)
    if document.balance_type == document.BalanceType.CUSTOMER:
        duplicate = duplicate.filter(customer=document.customer, currency=document.currency)
        existing = _existing_party_opening_for_currency(document.customer, document.currency)
        lines = [("1400", "debit"), ("3100", "credit")]
    elif document.balance_type == document.BalanceType.SUPPLIER:
        duplicate = duplicate.filter(supplier=document.supplier, currency=document.currency)
        existing = _existing_party_opening_for_currency(document.supplier, document.currency)
        lines = [("3100", "debit"), ("2100", "credit")]
    else:
        duplicate = duplicate.filter(financial_account=document.financial_account)
        existing = Decimal(str(document.financial_account.opening_balance or 0))
        if document.amount > 0:
            lines = [(financial_ledger_code(document.financial_account), "debit"), ("3100", "credit")]
        else:
            lines = [(financial_ledger_code(document.financial_account), "credit"), ("3100", "debit")]
    if duplicate.exclude(pk=document.pk).exists() or existing != 0:
        raise ValidationError("This target already has an opening balance. Clear legacy opening data before posting a controlled opening.")
    journal = create_system_journal(
        shop=document.shop, entry_date=document.opening_date,
        source_type="FINANCIAL_OPENING", source_id=document.pk,
        description=f"Opening balance: {target}", currency=document.currency,
        exchange_rate=document.exchange_rate_used,
        lines=[{"code": code, "side": side, "amount": abs(document.amount), "customer": document.customer, "supplier": document.supplier, "financial_account": document.financial_account} for code, side in lines],
        user=user,
    )
    if hasattr(target, "opening_balance_currency"):
        code = classify_currency_code(document.currency)
        update_fields = ["opening_balance", "opening_balance_currency"]
        if code == "AFN":
            target.opening_balance_afn = document.amount
            update_fields.append("opening_balance_afn")
        elif code == "USD":
            target.opening_balance_usd = document.amount
            update_fields.append("opening_balance_usd")
        target.opening_balance = document.amount
        target.opening_balance_currency = document.currency
        target.save(update_fields=update_fields)
    else:
        target.opening_balance = document.amount
        target.save(update_fields=["opening_balance"])
    document.opening_number = document.opening_number or _number(document)
    document.journal_entry = journal
    document.status = document.Status.POSTED
    document.posted_at = timezone.now()
    document.posted_by = user
    document.save(update_fields=["opening_number", "journal_entry", "status", "posted_at", "posted_by", "updated_at"])
    return document


@transaction.atomic
def void_financial_opening(document_id, *, user, reason):
    document = FinancialOpeningBalance.objects.select_for_update().select_related("shop", "currency", "customer", "supplier", "financial_account").get(pk=document_id)
    if document.status != document.Status.POSTED:
        raise ValidationError("Only posted opening balances can be cancelled.")
    reason = str(reason or "").strip()
    if not reason:
        raise ValidationError({"void_reason": "A void reason is required."})
    void_system_journal(shop=document.shop, source_type="FINANCIAL_OPENING", source_id=document.pk, user=user, reason=reason)
    target = _target(document)
    target = target.__class__.objects.select_for_update().get(pk=target.pk)
    if hasattr(target, "opening_balance_currency"):
        code = classify_currency_code(document.currency)
        update_fields = ["opening_balance", "opening_balance_currency"]
        if code == "AFN":
            target.opening_balance_afn = Decimal("0")
            target.opening_balance_afn_paid = Decimal("0")
            update_fields.extend(["opening_balance_afn", "opening_balance_afn_paid"])
        elif code == "USD":
            target.opening_balance_usd = Decimal("0")
            target.opening_balance_usd_paid = Decimal("0")
            update_fields.extend(["opening_balance_usd", "opening_balance_usd_paid"])

        if target.remaining_opening_balance_afn > 0:
            target.opening_balance = target.remaining_opening_balance_afn
            target.opening_balance_currency = resolve_shop_currency(target.shop_id, "AFN")
        elif target.remaining_opening_balance_usd > 0:
            target.opening_balance = target.remaining_opening_balance_usd
            target.opening_balance_currency = resolve_shop_currency(target.shop_id, "USD")
        else:
            target.opening_balance = Decimal("0")
            target.opening_balance_currency = None
        target.save(update_fields=update_fields)
    else:
        target.opening_balance = Decimal("0")
        target.save(update_fields=["opening_balance"])
    document.status = document.Status.VOID
    document.voided_at = timezone.now()
    document.voided_by = user
    document.void_reason = reason
    document.save(update_fields=["status", "voided_at", "voided_by", "void_reason", "updated_at"])
    return document
