"""Timbrado de CFDI — ``fin.invoice_stamps``.

Una fila por **intento** de timbrado, incluidos los fallidos: sin ellos no hay forma de
reconstruir por qué una factura no se timbró, y el error del PAC llega en un header HTTP que
se pierde en cuanto termina la petición.
"""

from datetime import datetime

from sqlalchemy import DateTime, ForeignKey, Index, Integer, String, Text, text
from sqlalchemy.orm import Mapped, mapped_column

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

_ALIVE = text("deleted_at IS NULL")

# Modos de timbrado. El host del PAC se deriva de aquí y de ningún otro lado.
MODE_TEST = "pruebas"
MODE_PROD = "produccion"
STAMPING_MODES = (MODE_TEST, MODE_PROD)

# RFC del proveedor de certificación según el entorno (CFDI.cs:16665 del sistema legado).
# Sirve para verificar que el timbre recibido viene del entorno que se pidió.
PAC_RFC_BY_MODE = {
    MODE_TEST: "SPR190613I52",
    MODE_PROD: "SCD110105654",
}

# Estados del intento.
STATUS_PENDING = "pendiente"
STATUS_STAMPED = "timbrado"
STATUS_ERROR = "error"


class InvoiceStamp(Base, TenantScopedMixin, TimestampMixin):
    """Intento de timbrado de una factura ante el PAC."""

    __tablename__ = "invoice_stamps"
    __table_args__ = (
        # Un UUID no puede repetirse: el SAT lo emite una sola vez. El índice es parcial
        # sobre uuid IS NOT NULL porque los intentos fallidos no traen UUID y serían todos
        # "iguales" entre sí bajo un único convencional.
        Index(
            "uq_fin_invoice_stamps_uuid",
            "uuid",
            unique=True,
            postgresql_where=text("uuid IS NOT NULL AND deleted_at IS NULL"),
            sqlite_where=text("uuid IS NOT NULL AND deleted_at IS NULL"),
        ),
        {"schema": "fin"},
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
    invoice_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("fin.invoices.id"), nullable=False, index=True
    )
    # Copiado de invoices.stamping_mode al transmitir y congelado aquí: es el registro de
    # contra qué entorno se timbró de verdad, aunque la factura cambie después.
    mode: Mapped[str] = mapped_column(String(12), nullable=False)
    status: Mapped[str] = mapped_column(
        String(12), nullable=False, server_default=text("'pendiente'"), index=True
    )

    # ----- Datos del Timbre Fiscal Digital (sólo si el PAC timbró) -----
    uuid: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True)
    stamped_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)  # FechaTimbrado
    pac_rfc: Mapped[str | None] = mapped_column(String(13), nullable=True)  # RfcProvCertif
    sat_cert_number: Mapped[str | None] = mapped_column(
        String(20), nullable=True
    )  # NoCertificadoSAT
    sat_seal: Mapped[str | None] = mapped_column(Text, nullable=True)  # SelloSAT
    cfd_seal: Mapped[str | None] = mapped_column(Text, nullable=True)  # SelloCFD

    # ----- Respuesta del PAC -----
    # El legado leía estos dos headers en una variable local que descartaba, así que su código
    # de respuesta y su saldo de folios se perdían siempre (CFDI.cs:19324-19336). Aquí se
    # persisten: sin ellos no se sabe cuántos folios quedan ni qué contestó el PAC.
    pac_code: Mapped[int | None] = mapped_column(Integer, nullable=True)  # header codigo
    pac_balance: Mapped[int | None] = mapped_column(Integer, nullable=True)  # header saldo
    error_message: Mapped[str | None] = mapped_column(Text, nullable=True)  # header errmsg

    # XML timbrado en MinIO. Sólo lo tienen los intentos exitosos: es el comprobante que se
    # descarga, y su clave lleva el UUID.
    xml_file_key: Mapped[str | None] = mapped_column(String(512), nullable=True)

    # ----- Rastro del intento en MinIO -----
    # El par enviado/recibido de CADA intento, incluidos los rechazados. Es lo único que
    # permite reconstruir por qué el PAC rechazó un comprobante: el XML sellado se construye
    # en memoria y se pierde al terminar la petición, y el cuerpo de la respuesta también.
    request_xml_file_key: Mapped[str | None] = mapped_column(String(512), nullable=True)
    response_xml_file_key: Mapped[str | None] = mapped_column(String(512), nullable=True)
    created_by: Mapped[str | None] = mapped_column(String(64), nullable=True)
