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.account_transaction import AccountTransaction, AccountTransactionType
from shop_management.models.data.account_transfer import AccountTransfer
from shop_management.services.account_controls import ensure_debit_allowed


def _number(document):
    prefix = f"{document.from_account.shop.name[:3].upper()}-FTR-{document.transfer_date.year}-"
    last = AccountTransfer.objects.filter(transfer_number__startswith=prefix).order_by("-transfer_number").values_list("transfer_number", flat=True).first()
    sequence = int(last.rsplit("-", 1)[-1]) + 1 if last else 1
    return f"{prefix}{sequence:06d}"


def _transaction(account, txn_type, amount, transaction_currency, exchange_rate, converted_amount, document, description, *, user, source_type):
    return AccountTransaction.objects.create(
        account=account, account_currency=account.currency,
        transaction_currency=transaction_currency, transaction_type=txn_type,
        amount=amount, exchange_rate=exchange_rate, converted_amount=converted_amount,
        transaction_date=document.transfer_date, description=description,
        reference=document.reference or "", source_type=source_type, source_id=str(document.pk),
        is_system_generated=True, created_by=user,
    )


@transaction.atomic
def post_finance_transfer(transfer_id, *, user):
    document = AccountTransfer.objects.select_for_update().select_related(
        "from_account__shop", "from_account__currency", "to_account__currency", "service_charge_account__currency"
    ).get(pk=transfer_id)
    if not document.uses_controlled_engine or document.status != AccountTransfer.Status.DRAFT:
        raise ValidationError("Only draft controlled transfers can be posted.")
    errors = {}
    if document.from_account_id == document.to_account_id or document.from_account.shop_id != document.to_account.shop_id:
        errors["to_account"] = "Accounts must be different and belong to the same branch."
    if document.amount <= 0 or document.exchange_rate <= 0:
        errors["amount"] = "Amount and exchange rate must be greater than zero."
    if document.from_currency_id != document.from_account.currency_id or document.to_currency_id != document.to_account.currency_id:
        errors["currency"] = "Transfer currencies must match their accounts."
    if document.service_charge < 0:
        errors["service_charge"] = "Service charge cannot be negative."
    if document.service_charge:
        charge_account = document.service_charge_account or document.from_account
        if charge_account.shop_id != document.from_account.shop_id or charge_account.currency_id != document.from_currency_id:
            errors["service_charge_account"] = "Charge account must belong to the branch and use the source currency."
    if errors:
        raise ValidationError(errors)
    document.converted_amount = AccountTransaction.compute_converted_amount(document.amount, document.exchange_rate)
    ensure_debit_allowed(
        document.from_account,
        document.amount + (document.service_charge if (document.service_charge_account_id in (None, document.from_account_id)) else Decimal("0")),
        label="account transfer",
    )
    reference = f" | {document.reference}" if document.reference else ""
    _transaction(document.from_account, AccountTransactionType.DEBIT, document.amount, document.from_currency, Decimal("1"), document.amount, document, f"Controlled transfer out #{document.pk}{reference}", user=user, source_type="ACCOUNT_TRANSFER_OUT")
    _transaction(document.to_account, AccountTransactionType.CREDIT, document.amount, document.from_currency, document.exchange_rate, document.converted_amount, document, f"Controlled transfer in #{document.pk}{reference}", user=user, source_type="ACCOUNT_TRANSFER_IN")
    if document.service_charge:
        charge_account = document.service_charge_account or document.from_account
        if charge_account_id := getattr(charge_account, "id", None):
            if charge_account_id != document.from_account_id:
                ensure_debit_allowed(charge_account, document.service_charge, label="transfer fee")
        _transaction(charge_account, AccountTransactionType.DEBIT, document.service_charge, document.from_currency, Decimal("1"), document.service_charge, document, f"Transfer service charge #{document.pk}{reference}", user=user, source_type="ACCOUNT_TRANSFER_FEE")
    document.transfer_number = document.transfer_number or _number(document)
    document.status = AccountTransfer.Status.POSTED
    document.posted_at = timezone.now()
    document.posted_by = user
    document.save(update_fields=["converted_amount", "transfer_number", "status", "posted_at", "posted_by", "updated_at"])
    return document


@transaction.atomic
def void_finance_transfer(transfer_id, *, user, reason):
    document = AccountTransfer.objects.select_for_update().select_related(
        "from_account__currency", "to_account__currency", "service_charge_account__currency"
    ).get(pk=transfer_id)
    if not document.uses_controlled_engine or document.status != AccountTransfer.Status.POSTED:
        raise ValidationError("Only posted controlled transfers can be cancelled.")
    if not str(reason or "").strip():
        raise ValidationError({"void_reason": "A void reason is required."})
    _transaction(document.from_account, AccountTransactionType.CREDIT, document.amount, document.from_currency, Decimal("1"), document.amount, document, f"Transfer void source #{document.pk} | {str(reason).strip()}", user=user, source_type="ACCOUNT_TRANSFER_VOID")
    _transaction(document.to_account, AccountTransactionType.DEBIT, document.amount, document.from_currency, document.exchange_rate, document.converted_amount, document, f"Transfer void destination #{document.pk} | {str(reason).strip()}", user=user, source_type="ACCOUNT_TRANSFER_VOID")
    if document.service_charge:
        charge_account = document.service_charge_account or document.from_account
        _transaction(charge_account, AccountTransactionType.CREDIT, document.service_charge, document.from_currency, Decimal("1"), document.service_charge, document, f"Transfer charge void #{document.pk} | {str(reason).strip()}", user=user, source_type="ACCOUNT_TRANSFER_FEE_VOID")
    document.status = AccountTransfer.Status.VOID
    document.voided_at = timezone.now()
    document.voided_by = user
    document.void_reason = str(reason).strip()
    document.save(update_fields=["status", "voided_at", "voided_by", "void_reason", "updated_at"])
    return document
