"""Folios auto-generados del ciclo comercial (Oportunidad → Solicitud → Cotización → Operación).

Formato: ``{LETRA}{AAAA}-{MM}-{NNN}-{DIR}`` (ej. ``O2025-08-001-E``):
- LETRA: entidad — ``O`` Oportunidad, ``S`` Solicitud, ``C`` Cotización, ``OP`` Operación/Embarque.
- ``AAAA-MM``: año-mes de creación.
- ``NNN``: consecutivo **mensual** por compañía y por entidad (reinicia cada mes).
- ``DIR``: ``I`` importación / ``E`` exportación (``X`` si aún no se define la dirección).

El consecutivo se toma de ``crm.folio_counters`` con bloqueo de fila para evitar
duplicados por concurrencia. En SQLite (pruebas) el ``FOR UPDATE`` se ignora sin error;
la unicidad la garantiza el índice único (tenant, company, entity, period).
"""

from __future__ import annotations

from datetime import date

from sqlalchemy import Integer, String, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column

from api.v1.common.base_models import BaseTimestampMixin, TenantScopedMixin
from core.database import Base

# Entidades válidas y su letra de folio (F = factura, EXP = expediente; sin dirección).
ENTITIES = ("O", "S", "C", "OP", "F", "EXP")
# Mapa dirección de operación → sufijo del folio.
_DIRECTION_SUFFIX = {"importacion": "I", "exportacion": "E"}


class FolioCounter(Base, TenantScopedMixin, BaseTimestampMixin):
    """Consecutivo mensual por compañía y entidad para armar los folios del ciclo."""

    __tablename__ = "folio_counters"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id", "company_id", "entity", "period", name="uq_crm_folio_counters_scope"
        ),
        {"schema": "crm"},
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
    entity: Mapped[str] = mapped_column(String(4), nullable=False)  # O | S | C | OP
    period: Mapped[str] = mapped_column(String(7), nullable=False)  # 'AAAA-MM'
    last_number: Mapped[int] = mapped_column(Integer, nullable=False, server_default=text("0"))


def direction_suffix(direction: str | None) -> str:
    """Devuelve la letra de dirección del folio (I/E) o 'X' si no está definida."""
    return _DIRECTION_SUFFIX.get(direction or "", "X")


def next_folio(
    db,
    tenant_id: int,
    company_id: int,
    entity: str,
    direction: str | None,
    on_date: date | None = None,
    with_direction: bool = True,
) -> str:
    """Genera el siguiente folio de una entidad, incrementando su consecutivo mensual.

    Reserva el número dentro de la transacción activa (no hace commit): el ``create_*``
    que lo invoca es quien confirma junto con la fila recién creada. ``with_direction=False``
    omite el sufijo I/E (p. ej. facturas → ``F2026-08-001``).
    """
    if entity not in ENTITIES:
        raise ValueError(f"Entidad de folio inválida: {entity!r}")
    on_date = on_date or date.today()
    period = on_date.strftime("%Y-%m")

    counter = (
        db.query(FolioCounter)
        .filter(
            FolioCounter.tenant_id == tenant_id,
            FolioCounter.company_id == company_id,
            FolioCounter.entity == entity,
            FolioCounter.period == period,
        )
        .with_for_update()
        .first()
    )
    if counter is None:
        counter = FolioCounter(
            tenant_id=tenant_id, company_id=company_id, entity=entity, period=period, last_number=0
        )
        db.add(counter)
        db.flush()

    counter.last_number = (counter.last_number or 0) + 1
    db.flush()

    sequence = f"{counter.last_number:03d}"
    if not with_direction:
        return f"{entity}{period}-{sequence}"
    return f"{entity}{period}-{sequence}-{direction_suffix(direction)}"
