"""Catálogo c_UsoCFDI y claves fiscales del receptor en crm.accounts.

Cierra las decisiones pendientes 1 y 5 del ticket de catálogos SAT: agrega
``sat.cfdi_uses`` y amarra el régimen fiscal y el uso de CFDI de la cuenta a los
catálogos, conservando las columnas de texto libre que ya existían.

Re-encadenada al integrar main: esta rama y la del CRM habían salido las dos de
d5e6f7a8b9c0, y con dos cabezas ``alembic upgrade head`` falla. La historia queda lineal,
con las migraciones de facturación detrás de las del CRM.

Revision ID: f7a8b9c0d1e2
Revises: g1h2i3j4k5l6
Create Date: 2026-08-07 00:00:00.000000

"""
from typing import Sequence, Union

import sqlalchemy as sa
from alembic import op

from api.v1.modules.fin.catalogs.seed_data import sync_catalogs

revision: str = "f7a8b9c0d1e2"
down_revision: Union[str, None] = "g1h2i3j4k5l6"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    # ---------- sat.cfdi_uses ----------
    op.create_table(
        "cfdi_uses",
        sa.Column("id", sa.Integer(), nullable=False),
        sa.Column("code", sa.String(length=4), nullable=False),
        sa.Column("description", sa.String(length=500), nullable=False),
        sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.text("true")),
        sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.text("now()")),
        sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.text("now()")),
        sa.PrimaryKeyConstraint("id"),
        schema="sat",
    )
    op.create_index("ix_sat_cfdi_uses_id", "cfdi_uses", ["id"], schema="sat")
    op.create_index("ix_sat_cfdi_uses_code", "cfdi_uses", ["code"], unique=True, schema="sat")

    # sync_catalogs es idempotente: siembra c_UsoCFDI y deja intactos los catálogos
    # que ya sembró la migración anterior.
    sync_catalogs(op.get_bind())

    # ---------- crm.accounts: claves fiscales del receptor ----------
    # Nullables: las cuentas existentes solo tienen el texto libre.
    op.add_column("accounts", sa.Column("tax_regime_id", sa.Integer(), nullable=True), schema="crm")
    op.add_column("accounts", sa.Column("cfdi_use_id", sa.Integer(), nullable=True), schema="crm")
    op.create_foreign_key(
        "fk_crm_accounts_tax_regime_id", "accounts", "tax_regimes",
        ["tax_regime_id"], ["id"], source_schema="crm", referent_schema="sat",
    )
    op.create_foreign_key(
        "fk_crm_accounts_cfdi_use_id", "accounts", "cfdi_uses",
        ["cfdi_use_id"], ["id"], source_schema="crm", referent_schema="sat",
    )

    # Backfill conservador: solo resuelve lo inequívoco. Se compara el texto libre
    # contra la clave del catálogo (p. ej. "601", "G03") y contra la descripción
    # exacta, sin distinguir mayúsculas ni espacios sobrantes. Lo que no case así se
    # queda en NULL para que lo revise el usuario: adivinar el régimen de un receptor
    # a partir de texto libre provoca CFDI rechazados.
    for column, catalog in [("tax_regime", "tax_regimes"), ("cfdi_use", "cfdi_uses")]:
        op.execute(
            f"""
            UPDATE crm.accounts AS a
               SET {column}_id = c.id
              FROM sat.{catalog} AS c
             WHERE a.{column}_id IS NULL
               AND a.{column} IS NOT NULL
               AND (
                     upper(btrim(a.{column})) = upper(c.code)
                  OR upper(btrim(a.{column})) = upper(c.description)
               )
            """
        )


def downgrade() -> None:
    op.drop_constraint("fk_crm_accounts_cfdi_use_id", "accounts", schema="crm", type_="foreignkey")
    op.drop_constraint("fk_crm_accounts_tax_regime_id", "accounts", schema="crm", type_="foreignkey")
    op.drop_column("accounts", "cfdi_use_id", schema="crm")
    op.drop_column("accounts", "tax_regime_id", schema="crm")
    op.drop_table("cfdi_uses", schema="sat")
