"""
Pre-migration script for laundry_management 19.0.11.0.0

Architecture change: standalone laundry.order / laundry.order.line / laundry.payment
models are replaced by _inherit = 'sale.order' / 'sale.order.line'.

This script runs BEFORE Odoo's model sync so that:
1. FK constraints from old wizard tables referencing laundry_order are dropped
2. Old wizard transient records are purged (they reference non-existent rows)
3. Stale ir.model.fields records pointing to old models are removed
4. Old ir.model entries are deleted (unblocking the ORM delete check)
5. Old physical tables are dropped

Without this, the ORM raises:
  "Another model is using the record you are trying to delete.
   Blocking model: Laundry Print Wizard (laundry.print.wizard),
   Blocking field: order_id"
"""

import logging

_logger = logging.getLogger(__name__)


# Old standalone models being removed in this version
_OLD_MODELS = [
    'laundry.order',
    'laundry.order.line',
    'laundry.payment',
    'laundry.order.line.addon',
    'laundry.register.payment.wizard',
    'laundry.product.wizard',
    'laundry.product.wizard.line',
    'laundry.category',
    'laundry.item.type',
]

# Physical tables that correspond to the old models
_OLD_TABLES = [
    'laundry_order',
    'laundry_order_line',
    'laundry_payment',
    'laundry_order_line_addon',
    'laundry_register_payment_wizard',
    'laundry_product_wizard',
    'laundry_product_wizard_line',
    'laundry_category',
    'laundry_item_type',
]

# Transient/wizard tables that may hold rows with FK refs to laundry_order
_WIZARD_TABLES = [
    'laundry_print_wizard',
    'laundry_session_wizard',
    'laundry_whatsapp_wizard',
    'laundry_register_payment_wizard',
    'laundry_product_wizard',
    'laundry_product_wizard_line',
]


def _table_exists(cr, table):
    cr.execute(
        "SELECT 1 FROM information_schema.tables "
        "WHERE table_schema = 'public' AND table_name = %s",
        (table,),
    )
    return bool(cr.fetchone())


def _column_exists(cr, table, column):
    cr.execute(
        "SELECT 1 FROM information_schema.columns "
        "WHERE table_schema = 'public' AND table_name = %s AND column_name = %s",
        (table, column),
    )
    return bool(cr.fetchone())


def _drop_fk_constraints_referencing(cr, referenced_table):
    """Drop all FK constraints in the DB that point at referenced_table."""
    cr.execute(
        """
        SELECT tc.table_name, tc.constraint_name
        FROM information_schema.table_constraints tc
        JOIN information_schema.referential_constraints rc
             ON tc.constraint_name = rc.constraint_name
             AND tc.table_schema   = rc.constraint_schema
        JOIN information_schema.table_constraints tc2
             ON rc.unique_constraint_name = tc2.constraint_name
             AND rc.unique_constraint_schema = tc2.table_schema
        WHERE tc.constraint_type = 'FOREIGN KEY'
          AND tc2.table_name = %s
          AND tc.table_schema = 'public'
        """,
        (referenced_table,),
    )
    rows = cr.fetchall()
    for src_table, constraint_name in rows:
        _logger.info(
            'pre_migrate: dropping FK constraint %s on %s (referenced %s)',
            constraint_name, src_table, referenced_table,
        )
        cr.execute(
            'ALTER TABLE "%s" DROP CONSTRAINT IF EXISTS "%s"' %
            (src_table, constraint_name)
        )


def migrate(cr, version):
    if not version:
        # Fresh install — nothing to clean up
        return

    _logger.info('pre_migrate laundry_management %s → 19.0.11.0.0 : start', version)

    # ── Step 1: Purge all transient wizard records ────────────────────────────
    # TransientModel rows expire naturally but DB rows persist during upgrade;
    # they hold FK refs that block constraint drops and table drops.
    for tbl in _WIZARD_TABLES:
        if _table_exists(cr, tbl):
            _logger.info('pre_migrate: truncating wizard table %s', tbl)
            cr.execute('TRUNCATE TABLE "%s" CASCADE' % tbl)

    # ── Step 2: Drop FK constraints pointing at old tables ────────────────────
    for tbl in _OLD_TABLES:
        if _table_exists(cr, tbl):
            _drop_fk_constraints_referencing(cr, tbl)

    # Also drop FKs on wizard tables that point at laundry_order (the primary blocker)
    # Example: laundry_print_wizard.order_id -> laundry_order.id
    for wizard_tbl in _WIZARD_TABLES:
        if not _table_exists(cr, wizard_tbl):
            continue
        # Find and drop any FK on this wizard table that references an old table
        cr.execute(
            """
            SELECT kcu.column_name, ccu.table_name AS foreign_table_name, tc.constraint_name
            FROM information_schema.table_constraints AS tc
            JOIN information_schema.key_column_usage AS kcu
                 ON tc.constraint_name = kcu.constraint_name
                 AND tc.table_schema   = kcu.table_schema
            JOIN information_schema.constraint_column_usage AS ccu
                 ON ccu.constraint_name = tc.constraint_name
                 AND ccu.table_schema   = tc.table_schema
            WHERE tc.constraint_type = 'FOREIGN KEY'
              AND tc.table_schema = 'public'
              AND tc.table_name = %s
              AND ccu.table_name = ANY(%s)
            """,
            (wizard_tbl, _OLD_TABLES),
        )
        for col, ref_tbl, constraint_name in cr.fetchall():
            _logger.info(
                'pre_migrate: dropping FK %s on %s.%s -> %s',
                constraint_name, wizard_tbl, col, ref_tbl,
            )
            cr.execute(
                'ALTER TABLE "%s" DROP CONSTRAINT IF EXISTS "%s"' %
                (wizard_tbl, constraint_name)
            )

    # ── Step 3: Remove stale ir.model.fields that ref old models ─────────────
    # These are the records that cause "Blocking model: laundry.print.wizard,
    # Blocking field: order_id" — the field record itself still has
    # relation = 'laundry.order', which makes the ORM think laundry.print.wizard
    # still depends on laundry.order.
    cr.execute(
        """
        DELETE FROM ir_model_fields
        WHERE relation = ANY(%s)
        """,
        (_OLD_MODELS,),
    )
    deleted = cr.rowcount
    _logger.info('pre_migrate: deleted %d stale ir.model.fields rows', deleted)

    # Also remove fields whose model itself is one of the old models
    cr.execute(
        """
        DELETE FROM ir_model_fields
        WHERE model IN %s
        """,
        (tuple(_OLD_MODELS),),
    )
    _logger.info('pre_migrate: deleted %d ir.model.fields for old models', cr.rowcount)

    # ── Step 4: Remove stale ir.model.fields_by_name cache ───────────────────
    # In some Odoo versions there is a separate constraint/index table.
    # Safe to attempt; ignore if table doesn't exist.
    if _table_exists(cr, 'ir_model_constraint'):
        cr.execute(
            """
            DELETE FROM ir_model_constraint imc
            USING ir_model im
            WHERE imc.model = im.id
              AND im.model = ANY(%s)
            """,
            (_OLD_MODELS,),
        )
        _logger.info('pre_migrate: removed %d ir.model.constraint rows', cr.rowcount)

    if _table_exists(cr, 'ir_model_relation'):
        cr.execute(
            """
            DELETE FROM ir_model_relation imr
            USING ir_model im
            WHERE imr.model = im.id
              AND im.model = ANY(%s)
            """,
            (_OLD_MODELS,),
        )
        _logger.info('pre_migrate: removed %d ir.model.relation rows', cr.rowcount)

    # ── Step 5: Remove ir.model entries for the old models ───────────────────
    cr.execute(
        "DELETE FROM ir_model WHERE model = ANY(%s)",
        (_OLD_MODELS,),
    )
    _logger.info('pre_migrate: deleted %d ir.model rows', cr.rowcount)

    # ── Step 6: Drop old physical tables ─────────────────────────────────────
    for tbl in reversed(_OLD_TABLES):  # reverse to respect FK order
        if _table_exists(cr, tbl):
            _logger.info('pre_migrate: dropping table %s', tbl)
            cr.execute('DROP TABLE IF EXISTS "%s" CASCADE' % tbl)

    _logger.info('pre_migrate laundry_management: complete')
