"""Document number allocation — Phase 5.1 DocumentNumberSequence."""
from __future__ import annotations

from uuid import UUID

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.foundation import DocumentNumberSequence
from app.models.operational import BusinessDocument
from app.models.posting import Voucher
from app.models.receivable_payable import CustomerAccount, ReceivableInvoice, Settlement, SupplierAccount
from app.models.treasury import PaymentVoucher, ReceiptVoucher

# document_type → (prefix, padding)
_DEFAULTS: dict[str, tuple[str, int]] = {
    "voucher": ("V-", 6),
    "customer": ("C-", 5),
    "supplier": ("S-", 5),
    "ar_invoice": ("ARI-", 6),
    "settlement": ("STL-", 6),
    "receipt": ("RCV-", 6),
    "payment": ("PAY-", 6),
    "inventory_item": ("ITM-", 5),
    "warehouse": ("WH-", 4),
}

_OPS_PREFIX: dict[tuple[str, str], str] = {
    ("sales", "proforma"): "PF-",
    ("sales", "invoice"): "SI-",
    ("sales", "order"): "SO-",
    ("sales", "return"): "SR-",
    ("sales", "pre_receipt"): "PR-",
    ("sales", "opportunity"): "OPP-",
    ("purchase", "request"): "PRQ-",
    ("purchase", "order"): "PO-",
    ("purchase", "invoice"): "PI-",
    ("purchase", "goods_receipt"): "GR-",
    ("purchase", "return"): "PRT-",
    ("purchase", "prepayment"): "PP-",
    ("inventory", "transfer"): "TR-",
    ("inventory", "adjustment"): "ADJ-",
    ("inventory", "receipt"): "INR-",
    ("inventory", "issue"): "INI-",
    ("treasury", "receipt"): "RCV-",
    ("treasury", "payment"): "PAY-",
}


def ops_document_type(module: str, doc_type: str) -> str:
    key = f"ops.{module}.{doc_type}"
    return key[:50]


def ops_prefix(module: str, doc_type: str) -> str:
    if (module, doc_type) in _OPS_PREFIX:
        return _OPS_PREFIX[(module, doc_type)]
    slug = "".join(ch for ch in doc_type.upper() if ch.isalnum())[:4] or "DOC"
    return f"{slug}-"


class DocumentNumberService:
    """Allocate sequential human-facing numbers per tenant + document_type."""

    def __init__(self, session: AsyncSession) -> None:
        self.session = session

    async def peek(self, tenant_id: UUID, document_type: str) -> dict:
        seq = await self._get_or_create(tenant_id, document_type, for_update=False)
        return {
            "document_type": seq.document_type,
            "prefix": seq.prefix or "",
            "next_number": seq.next_number,
            "padding": seq.padding,
            "preview": self._format(seq),
        }

    async def allocate(self, tenant_id: UUID, document_type: str) -> str:
        seq = await self._get_or_create(tenant_id, document_type, for_update=True)
        for _ in range(1000):
            candidate = self._format(seq)
            seq.next_number += 1
            if not await self._exists(tenant_id, document_type, candidate):
                await self.session.flush()
                return candidate
        raise RuntimeError(f"Could not allocate number for {document_type}")

    async def allocate_if_blank(
        self, tenant_id: UUID, document_type: str, provided: str | None
    ) -> str:
        value = (provided or "").strip()
        if value:
            return value
        return await self.allocate(tenant_id, document_type)

    def _defaults_for(self, document_type: str) -> tuple[str, int]:
        if document_type in _DEFAULTS:
            return _DEFAULTS[document_type]
        if document_type.startswith("ops."):
            parts = document_type.split(".", 2)
            if len(parts) == 3:
                return ops_prefix(parts[1], parts[2]), 6
        return ("DOC-", 6)

    async def _get_or_create(
        self, tenant_id: UUID, document_type: str, *, for_update: bool
    ) -> DocumentNumberSequence:
        stmt = select(DocumentNumberSequence).where(
            DocumentNumberSequence.tenant_id == tenant_id,
            DocumentNumberSequence.document_type == document_type,
        )
        if for_update:
            stmt = stmt.with_for_update()
        result = await self.session.execute(stmt)
        seq = result.scalar_one_or_none()
        if seq is not None:
            return seq

        prefix, padding = self._defaults_for(document_type)
        seq = DocumentNumberSequence(
            tenant_id=tenant_id,
            document_type=document_type,
            prefix=prefix,
            next_number=1,
            padding=padding,
        )
        self.session.add(seq)
        await self.session.flush()

        if for_update:
            # Re-select locked row after insert (concurrent creators)
            stmt2 = (
                select(DocumentNumberSequence)
                .where(
                    DocumentNumberSequence.tenant_id == tenant_id,
                    DocumentNumberSequence.document_type == document_type,
                )
                .with_for_update()
            )
            result2 = await self.session.execute(stmt2)
            locked = result2.scalar_one()
            return locked
        return seq

    @staticmethod
    def _format(seq: DocumentNumberSequence) -> str:
        prefix = seq.prefix or ""
        return f"{prefix}{str(seq.next_number).zfill(seq.padding)}"

    async def _exists(self, tenant_id: UUID, document_type: str, number: str) -> bool:
        if document_type == "voucher":
            stmt = select(Voucher.id).where(
                Voucher.tenant_id == tenant_id, Voucher.voucher_number == number
            )
        elif document_type == "customer":
            stmt = select(CustomerAccount.id).where(
                CustomerAccount.tenant_id == tenant_id, CustomerAccount.code == number
            )
        elif document_type == "supplier":
            stmt = select(SupplierAccount.id).where(
                SupplierAccount.tenant_id == tenant_id, SupplierAccount.code == number
            )
        elif document_type == "ar_invoice":
            stmt = select(ReceivableInvoice.id).where(
                ReceivableInvoice.tenant_id == tenant_id,
                ReceivableInvoice.invoice_number == number,
            )
        elif document_type == "settlement":
            stmt = select(Settlement.id).where(
                Settlement.tenant_id == tenant_id, Settlement.settlement_number == number
            )
        elif document_type == "receipt":
            stmt = select(ReceiptVoucher.id).where(
                ReceiptVoucher.tenant_id == tenant_id, ReceiptVoucher.receipt_number == number
            )
        elif document_type == "payment":
            stmt = select(PaymentVoucher.id).where(
                PaymentVoucher.tenant_id == tenant_id, PaymentVoucher.payment_number == number
            )
        elif document_type.startswith("ops."):
            parts = document_type.split(".", 2)
            module = parts[1] if len(parts) >= 2 else ""
            stmt = select(BusinessDocument.id).where(
                BusinessDocument.tenant_id == tenant_id,
                BusinessDocument.module == module,
                BusinessDocument.number == number,
            )
        else:
            return False
        result = await self.session.execute(stmt.limit(1))
        return result.scalar_one_or_none() is not None
