"""Invoice-specific, fully allocated received payments; no wallet or invoice writes."""
from contextlib import contextmanager
from django.core.exceptions import ValidationError
from django.utils import timezone
from apps.customers.models import Customer
from apps.devices.locking import persisted_pk
from apps.service.handover_services import _authorized_case
from apps.service.locking import share
from apps.service.models import ServiceCase
from .models import ServiceInvoice, ServicePayment, PaymentAllocation, ReceiptSequence, ServicePaymentReceipt, PaymentReversal, ServiceFinancialRelease
from .money import decimal_value
from .services import text
from .payment_queries import _locked_summary

RECEIVE = "commercial.receive_servicepayment"
REVERSE = "commercial.reverse_servicepayment"
RELEASE = "commercial.authorize_due_release"


@contextmanager
def _context(actor, invoice, permission):
    snapshot = ServiceInvoice.objects.select_related("service_case").get(pk=persisted_pk(invoice, ServiceInvoice))
    with _authorized_case(snapshot.service_case, actor, permission) as (case_snapshot, actor):
        # Customer may be inactive: historical debt still exists. This SHARE lock
        # stabilizes receipt identity and precedes Case, matching handover order.
        customer = share(Customer, case_snapshot.customer_id)
        case = ServiceCase.objects.select_for_update().get(pk=case_snapshot.pk)
        row = ServiceInvoice.objects.select_for_update().get(pk=snapshot.pk)
        if row.status != "FINALIZED" or case.status == "CANCELLED" or customer.company_id != case.company_id or row.service_center_id != case.service_center_id:
            raise ValidationError("Payment requires a consistent finalized service invoice.")
        yield actor, case, row, customer


def _expected(invoice, value):
    state = _locked_summary(invoice)
    if not isinstance(value, str) or value != state["revision"]:
        raise ValidationError("Settlement changed; reload the balance and financial evidence.")
    return state


def receive_service_payment(*, actor, invoice, expected_revision, amount, method, reference="", note=""):
    value = decimal_value(amount)
    reference, note = text(reference, 200), text(note, 2000)
    if value <= 0:
        raise ValidationError("Receive a positive amount; zero-due invoices require no payment.")
    with _context(actor, invoice, RECEIVE) as (actor, case, row, customer):
        state = _expected(row, expected_revision)
        if value > state["balance"]:
            raise ValidationError("Payment exceeds remaining customer liability; deposits and credits are unsupported.")
        now = timezone.now()
        payment = ServicePayment(invoice=row, service_center_id=row.service_center_id, amount=value, currency=row.currency,
            method=method, reference=reference, received_at=now, received_by=actor, note=note)
        payment._persist()
        PaymentAllocation(payment=payment, invoice=row, amount=value, allocated_by=actor)._persist()
        sequence, _ = ReceiptSequence.objects.select_for_update().get_or_create(service_center_id=row.service_center_id)
        if sequence.next_value >= 10**18:
            raise ValidationError("Receipt numbering exhausted.")
        number = f"RCT-{sequence.next_value:08d}"
        sequence.next_value += 1
        sequence.save(update_fields=["next_value"])
        center = row.service_center
        ServicePaymentReceipt(payment=payment, service_center=center, number=number, amount=value, currency=row.currency,
            method=method, reference=reference, invoice_number=row.number,
            customer_snapshot=dict(id=str(customer.pk), number=customer.customer_number, name=customer.display_name),
            center_snapshot=dict(id=str(center.pk), code=center.code, name=center.name), issued_by=actor, issued_at=now)._persist()
        return payment


def reverse_service_payment(*, actor, payment, expected_revision, reason):
    snapshot = ServicePayment.objects.select_related("invoice").get(pk=persisted_pk(payment, ServicePayment))
    reason = text(reason, 2000, required=True)
    with _context(actor, snapshot.invoice, REVERSE) as (actor, case, invoice, _):
        _expected(invoice, expected_revision)
        row = ServicePayment.objects.select_for_update().get(pk=snapshot.pk)
        if row.status != "POSTED":
            raise ValidationError("Payment was already reversed.")
        evidence = PaymentReversal(payment=row, amount=row.amount, reversed_by=actor, reversed_at=timezone.now(), reason=reason)
        evidence._persist()
        row.status = "VOIDED"
        row._persist()
        # Do not create an unsecured delivered balance by reversing the money
        # that authorized handover. A prior due-release must cover that balance.
        if case.status in ("DELIVERED", "CLOSED") and not _locked_summary(invoice)["financially_clear"]:
            raise ValidationError("Post-handover reversal requires existing due-release coverage; further correction workflows are deferred.")
        return evidence


def authorize_due_release(*, actor, invoice, expected_revision, reason, reference=""):
    reason, reference = text(reason, 2000, required=True), text(reference, 200)
    with _context(actor, invoice, RELEASE) as (actor, case, row, _):
        state = _expected(row, expected_revision)
        if case.status not in ("QC_PASSED", "READY_FOR_DELIVERY") or state["balance"] <= 0 or state["financially_clear"]:
            raise ValidationError("Authorize an outstanding, uncovered balance before handover after successful QC.")
        release = ServiceFinancialRelease(invoice=row, outstanding_amount=state["balance"], authorized_by=actor,
            authorized_at=timezone.now(), reason=reason, reference=reference)
        release._persist()
        return release


def require_delivery_financial_clearance(*, service_case):
    """Trusted handover hook. Caller holds Case FOR UPDATE; acquire no dependencies."""
    invoice = ServiceInvoice.objects.filter(service_case=service_case, status="FINALIZED").first()
    if invoice is not None and not _locked_summary(invoice)["financially_clear"]:
        raise ValidationError("Outstanding customer liability requires payment or an authorized financial release before handover.")
