"""Catálogo de conceptos de facturación — ``fin.concepts``.

A diferencia de los catálogos del SAT, este es **propio de cada empresa**: cada
concepto que la empresa factura (flete internacional, despacho, almacenaje…) se
registra una vez y queda amarrado a la clave de producto/servicio del SAT que le
corresponde.

La relación con ``sat.products_services`` es **1:1 por empresa**: si dos conceptos
compartieran la misma clave ProdServ, al timbrar no habría forma de saber cuál
descripción corresponde a la clave, así que la unicidad se garantiza por índice y se
valida además en el service para devolver un 409 con mensaje entendible.
"""

from sqlalchemy import Boolean, ForeignKey, Index, Integer, Numeric, String, Text, text
from sqlalchemy.orm import Mapped, mapped_column, relationship

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

from ..catalogs.models import ProductService, TaxObject, UnitOfMeasure  # noqa: F401  (resuelve las relaciones)

# Los índices son parciales (``WHERE deleted_at IS NULL``): un concepto dado de baja
# lógica libera su clave y su código para uno nuevo.
_ALIVE = text("deleted_at IS NULL")


class Concept(Base, TenantScopedMixin, TimestampMixin):
    """Concepto facturable de una empresa, ligado a una clave ProdServ del SAT."""

    __tablename__ = "concepts"
    __table_args__ = (
        Index(
            "uq_fin_concepts_code",
            "tenant_id", "company_id", "code",
            unique=True, postgresql_where=_ALIVE, sqlite_where=_ALIVE,
        ),
        Index(
            "uq_fin_concepts_product_service",
            "tenant_id", "company_id", "product_service_id",
            unique=True, postgresql_where=_ALIVE, sqlite_where=_ALIVE,
        ),
        {"schema": "fin"},
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
    code: Mapped[str] = mapped_column(String(40), nullable=False)  # clave interna del concepto
    description: Mapped[str] = mapped_column(String(500), nullable=False)
    product_service_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("sat.products_services.id"), nullable=False, index=True
    )
    unit_of_measure_id: Mapped[int | None] = mapped_column(
        Integer, ForeignKey("sat.units_of_measure.id"), nullable=True
    )
    tax_object_id: Mapped[int | None] = mapped_column(
        Integer, ForeignKey("sat.tax_objects.id"), nullable=True
    )
    unit_price: Mapped[float | None] = mapped_column(Numeric(14, 2), nullable=True)
    currency: Mapped[str] = mapped_column(String(3), nullable=False, server_default=text("'MXN'"))
    is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("true"))
    notes: Mapped[str | None] = mapped_column(Text, nullable=True)
    created_by: Mapped[str | None] = mapped_column(String(64), nullable=True)
    updated_by: Mapped[str | None] = mapped_column(String(64), nullable=True)

    # Cargadas con selectinload para que el listado no dispare N+1 consultas.
    product_service: Mapped["ProductService"] = relationship("ProductService", lazy="selectin")
    unit_of_measure: Mapped["UnitOfMeasure | None"] = relationship("UnitOfMeasure", lazy="selectin")
    tax_object: Mapped["TaxObject | None"] = relationship("TaxObject", lazy="selectin")
