<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
 * Re-link orphaned CustomField records to their ModelType.
 *
 * Legacy data created one CustomField definition per composant/piece instead
 * of sharing a single definition on the ModelType. This migration:
 *  1. Finds orphaned CustomField used by composants → elects one canonical per (ModelType, name)
 *  2. Same for pieces
 *  3. Re-points CustomFieldValue to the canonical
 *  4. Sets typecomposantid / typepieceid on the canonical
 *  5. Deletes now-unused duplicate CustomField rows
 */
final class Version20260404_RelinkOrphanedCustomFields extends AbstractMigration
{
    public function getDescription(): string
    {
        return 'Re-link orphaned custom_fields to their model_types via composants and pieces';
    }

    public function up(Schema $schema): void
    {
        // ── COMPOSANTS ──────────────────────────────────────────────────
        // For each (model_type, field_name) group of orphaned fields used by composants,
        // pick the one with the smallest id as canonical, set its typecomposantid,
        // re-point all values to it, then delete the duplicates.
        $this->addSql(<<<'SQL'
            -- Step 1: Create temp table with canonical mapping for COMPOSANT orphans
            CREATE TEMP TABLE _cf_composant_canonical AS
            SELECT DISTINCT ON (c.typecomposantid, cf.name)
                cf.id         AS canonical_id,
                c.typecomposantid AS mt_id,
                cf.name       AS cf_name
            FROM custom_fields cf
            INNER JOIN custom_field_values cfv ON cfv.customfieldid = cf.id
            INNER JOIN composants c ON c.id = cfv.composantid
            WHERE cf.typecomposantid IS NULL
              AND cf.typepieceid IS NULL
              AND cf.typeproductid IS NULL
              AND cf.machineid IS NULL
              AND c.typecomposantid IS NOT NULL
            ORDER BY c.typecomposantid, cf.name, cf.id;
        SQL);

        $this->addSql(<<<'SQL'
            -- Step 2: Create temp table listing ALL orphaned composant field ids with their canonical
            CREATE TEMP TABLE _cf_composant_all AS
            SELECT cf.id AS orphan_id, canon.canonical_id
            FROM custom_fields cf
            INNER JOIN custom_field_values cfv ON cfv.customfieldid = cf.id
            INNER JOIN composants c ON c.id = cfv.composantid
            INNER JOIN _cf_composant_canonical canon
                ON canon.mt_id = c.typecomposantid AND canon.cf_name = cf.name
            WHERE cf.typecomposantid IS NULL
              AND cf.typepieceid IS NULL
              AND cf.typeproductid IS NULL
              AND cf.machineid IS NULL
              AND c.typecomposantid IS NOT NULL
              AND cf.id != canon.canonical_id;
        SQL);

        $this->addSql(<<<'SQL'
            -- Step 3: Re-point custom_field_values from duplicates to canonical
            UPDATE custom_field_values cfv
            SET customfieldid = dup.canonical_id
            FROM _cf_composant_all dup
            WHERE cfv.customfieldid = dup.orphan_id;
        SQL);

        $this->addSql(<<<'SQL'
            -- Step 4: Set typecomposantid on canonical fields
            UPDATE custom_fields cf
            SET typecomposantid = canon.mt_id
            FROM _cf_composant_canonical canon
            WHERE cf.id = canon.canonical_id;
        SQL);

        $this->addSql(<<<'SQL'
            -- Step 5: Delete duplicate (non-canonical) custom_fields for composants
            DELETE FROM custom_fields
            WHERE id IN (SELECT orphan_id FROM _cf_composant_all);
        SQL);

        $this->addSql('DROP TABLE IF EXISTS _cf_composant_all');
        $this->addSql('DROP TABLE IF EXISTS _cf_composant_canonical');

        // ── PIECES ───────────���────────────────────────────���─────────────
        $this->addSql(<<<'SQL'
            CREATE TEMP TABLE _cf_piece_canonical AS
            SELECT DISTINCT ON (p.typepieceid, cf.name)
                cf.id         AS canonical_id,
                p.typepieceid AS mt_id,
                cf.name       AS cf_name
            FROM custom_fields cf
            INNER JOIN custom_field_values cfv ON cfv.customfieldid = cf.id
            INNER JOIN pieces p ON p.id = cfv.pieceid
            WHERE cf.typecomposantid IS NULL
              AND cf.typepieceid IS NULL
              AND cf.typeproductid IS NULL
              AND cf.machineid IS NULL
              AND p.typepieceid IS NOT NULL
            ORDER BY p.typepieceid, cf.name, cf.id;
        SQL);

        $this->addSql(<<<'SQL'
            CREATE TEMP TABLE _cf_piece_all AS
            SELECT cf.id AS orphan_id, canon.canonical_id
            FROM custom_fields cf
            INNER JOIN custom_field_values cfv ON cfv.customfieldid = cf.id
            INNER JOIN pieces p ON p.id = cfv.pieceid
            INNER JOIN _cf_piece_canonical canon
                ON canon.mt_id = p.typepieceid AND canon.cf_name = cf.name
            WHERE cf.typecomposantid IS NULL
              AND cf.typepieceid IS NULL
              AND cf.typeproductid IS NULL
              AND cf.machineid IS NULL
              AND p.typepieceid IS NOT NULL
              AND cf.id != canon.canonical_id;
        SQL);

        $this->addSql(<<<'SQL'
            UPDATE custom_field_values cfv
            SET customfieldid = dup.canonical_id
            FROM _cf_piece_all dup
            WHERE cfv.customfieldid = dup.orphan_id;
        SQL);

        $this->addSql(<<<'SQL'
            UPDATE custom_fields cf
            SET typepieceid = canon.mt_id
            FROM _cf_piece_canonical canon
            WHERE cf.id = canon.canonical_id;
        SQL);

        $this->addSql(<<<'SQL'
            DELETE FROM custom_fields
            WHERE id IN (SELECT orphan_id FROM _cf_piece_all);
        SQL);

        $this->addSql('DROP TABLE IF EXISTS _cf_piece_all');
        $this->addSql('DROP TABLE IF EXISTS _cf_piece_canonical');
    }

    public function down(Schema $schema): void
    {
        // Data migration — not reversible. The old per-entity duplicates are gone.
        $this->addSql('SELECT 1');
    }
}
