<?php

declare(strict_types=1);

namespace DoctrineMigrations;

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

/**
 * Align all FKs pointing to `pieces.id` with what entities declare
 * (ON DELETE CASCADE / SET NULL). Cleans up pre-existing orphan rows
 * inserted before the constraints existed, so the new FKs can be added.
 *
 * Mirror of Version20260506140000_FixComposantCascadeFKs for the Piece side.
 */
final class Version20260528090000_FixPieceCascadeFKs extends AbstractMigration
{
    public function getDescription(): string
    {
        return 'Align CASCADE/SET NULL FKs on pieces references (machine_piece_links, composant_piece_slots, piece_product_slots, documents, custom_field_values, piece_constructeur_links); cleanup pre-existing orphans';
    }

    public function up(Schema $schema): void
    {
        // =========================================================================
        // 1. Audit log : snapshot des rows orphelines avant suppression.
        // =========================================================================
        $this->addSql(<<<'SQL'
            INSERT INTO audit_logs (id, entitytype, entityid, action, snapshot, actorprofileid, createdat)
            SELECT
              'cl' || substring(md5(random()::text || clock_timestamp()::text), 1, 24),
              'machine_piece_link',
              l.id,
              'delete',
              json_build_object(
                'id', l.id,
                'machineId', l.machineid,
                'pieceId', l.pieceid,
                'note', 'Cleaned by FK cascade fix migration (Version20260528090000) - referenced piece no longer existed'
              ),
              NULL,
              NOW()
            FROM machine_piece_links l
            WHERE l.pieceid IS NOT NULL
              AND l.pieceid NOT IN (SELECT id FROM pieces)
            SQL);

        $this->addSql(<<<'SQL'
            INSERT INTO audit_logs (id, entitytype, entityid, action, snapshot, actorprofileid, createdat)
            SELECT
              'cl' || substring(md5(random()::text || clock_timestamp()::text), 1, 24),
              'piece_product_slot',
              s.id,
              'delete',
              json_build_object(
                'id', s.id,
                'pieceId', s.pieceid,
                'note', 'Cleaned by FK cascade fix migration (Version20260528090000) - referenced piece no longer existed'
              ),
              NULL,
              NOW()
            FROM piece_product_slots s
            WHERE s.pieceid NOT IN (SELECT id FROM pieces)
            SQL);

        $this->addSql(<<<'SQL'
            INSERT INTO audit_logs (id, entitytype, entityid, action, snapshot, actorprofileid, createdat)
            SELECT
              'cl' || substring(md5(random()::text || clock_timestamp()::text), 1, 24),
              'document',
              d.id,
              'delete',
              json_build_object(
                'id', d.id,
                'name', d.name,
                'filename', d.filename,
                'pieceId', d.pieceid,
                'note', 'Cleaned by FK cascade fix migration (Version20260528090000) - referenced piece no longer existed'
              ),
              NULL,
              NOW()
            FROM documents d
            WHERE d.pieceid IS NOT NULL
              AND d.pieceid NOT IN (SELECT id FROM pieces)
            SQL);

        $this->addSql(<<<'SQL'
            INSERT INTO audit_logs (id, entitytype, entityid, action, snapshot, actorprofileid, createdat)
            SELECT
              'cl' || substring(md5(random()::text || clock_timestamp()::text), 1, 24),
              'custom_field_value',
              v.id,
              'delete',
              json_build_object(
                'id', v.id,
                'pieceId', v.pieceid,
                'note', 'Cleaned by FK cascade fix migration (Version20260528090000) - referenced piece no longer existed'
              ),
              NULL,
              NOW()
            FROM custom_field_values v
            WHERE v.pieceid IS NOT NULL
              AND v.pieceid NOT IN (SELECT id FROM pieces)
            SQL);

        $this->addSql(<<<'SQL'
            INSERT INTO audit_logs (id, entitytype, entityid, action, snapshot, actorprofileid, createdat)
            SELECT
              'cl' || substring(md5(random()::text || clock_timestamp()::text), 1, 24),
              'piece_constructeur_link',
              l.id,
              'delete',
              json_build_object(
                'id', l.id,
                'pieceId', l.pieceid,
                'constructeurId', l.constructeurid,
                'note', 'Cleaned by FK cascade fix migration (Version20260528090000) - referenced piece no longer existed'
              ),
              NULL,
              NOW()
            FROM piece_constructeur_links l
            WHERE l.pieceid NOT IN (SELECT id FROM pieces)
            SQL);

        // =========================================================================
        // 2. Nettoyage des orphelins (avant ADD CONSTRAINT, sinon PG rejette).
        // =========================================================================
        $this->addSql(<<<'SQL'
            DELETE FROM machine_piece_links
            WHERE pieceid IS NOT NULL
              AND pieceid NOT IN (SELECT id FROM pieces)
            SQL);

        $this->addSql(<<<'SQL'
            UPDATE composant_piece_slots SET selectedpieceid = NULL
            WHERE selectedpieceid IS NOT NULL
              AND selectedpieceid NOT IN (SELECT id FROM pieces)
            SQL);

        $this->addSql(<<<'SQL'
            DELETE FROM piece_product_slots
            WHERE pieceid NOT IN (SELECT id FROM pieces)
            SQL);

        $this->addSql(<<<'SQL'
            DELETE FROM documents
            WHERE pieceid IS NOT NULL
              AND pieceid NOT IN (SELECT id FROM pieces)
            SQL);

        $this->addSql(<<<'SQL'
            DELETE FROM custom_field_values
            WHERE pieceid IS NOT NULL
              AND pieceid NOT IN (SELECT id FROM pieces)
            SQL);

        $this->addSql(<<<'SQL'
            DELETE FROM piece_constructeur_links
            WHERE pieceid NOT IN (SELECT id FROM pieces)
            SQL);

        $this->addSql(<<<'SQL'
            DELETE FROM piece_products
            WHERE piece_id NOT IN (SELECT id FROM pieces)
            SQL);

        // =========================================================================
        // 3. Drop des éventuelles FK existantes vers `pieces` (quel que soit leur
        //    nom historique), puis ADD CONSTRAINT avec le bon ON DELETE.
        // =========================================================================
        $this->dropFksReferencingPieces('machine_piece_links', 'pieceid');
        $this->addSql(<<<'SQL'
            ALTER TABLE machine_piece_links ADD CONSTRAINT fk_mpl_piece
              FOREIGN KEY (pieceid) REFERENCES pieces(id) ON DELETE CASCADE
            SQL);

        $this->dropFksReferencingPieces('composant_piece_slots', 'selectedpieceid');
        $this->addSql(<<<'SQL'
            ALTER TABLE composant_piece_slots ADD CONSTRAINT fk_cps_selected_piece
              FOREIGN KEY (selectedpieceid) REFERENCES pieces(id) ON DELETE SET NULL
            SQL);

        $this->dropFksReferencingPieces('piece_product_slots', 'pieceid');
        $this->addSql(<<<'SQL'
            ALTER TABLE piece_product_slots ADD CONSTRAINT fk_pps_piece
              FOREIGN KEY (pieceid) REFERENCES pieces(id) ON DELETE CASCADE
            SQL);

        $this->dropFksReferencingPieces('documents', 'pieceid');
        $this->addSql(<<<'SQL'
            ALTER TABLE documents ADD CONSTRAINT fk_documents_piece
              FOREIGN KEY (pieceid) REFERENCES pieces(id) ON DELETE CASCADE
            SQL);

        $this->dropFksReferencingPieces('custom_field_values', 'pieceid');
        $this->addSql(<<<'SQL'
            ALTER TABLE custom_field_values ADD CONSTRAINT fk_cfv_piece
              FOREIGN KEY (pieceid) REFERENCES pieces(id) ON DELETE CASCADE
            SQL);

        $this->dropFksReferencingPieces('piece_constructeur_links', 'pieceid');
        $this->addSql(<<<'SQL'
            ALTER TABLE piece_constructeur_links ADD CONSTRAINT fk_pcl_piece
              FOREIGN KEY (pieceid) REFERENCES pieces(id) ON DELETE CASCADE
            SQL);
    }

    public function down(Schema $schema): void
    {
        $this->addSql('ALTER TABLE machine_piece_links      DROP CONSTRAINT IF EXISTS fk_mpl_piece');
        $this->addSql('ALTER TABLE composant_piece_slots    DROP CONSTRAINT IF EXISTS fk_cps_selected_piece');
        $this->addSql('ALTER TABLE piece_product_slots      DROP CONSTRAINT IF EXISTS fk_pps_piece');
        $this->addSql('ALTER TABLE documents                DROP CONSTRAINT IF EXISTS fk_documents_piece');
        $this->addSql('ALTER TABLE custom_field_values      DROP CONSTRAINT IF EXISTS fk_cfv_piece');
        $this->addSql('ALTER TABLE piece_constructeur_links DROP CONSTRAINT IF EXISTS fk_pcl_piece');
    }

    /**
     * Drop every FK on $table.$column that references the `pieces` table,
     * regardless of its historic name. Idempotent.
     */
    private function dropFksReferencingPieces(string $table, string $column): void
    {
        $sql = <<<SQL
            DO \$\$
            DECLARE
              fk_name TEXT;
            BEGIN
              FOR fk_name IN
                SELECT tc.constraint_name
                FROM information_schema.table_constraints tc
                JOIN information_schema.key_column_usage kcu
                  ON kcu.constraint_name = tc.constraint_name
                 AND kcu.table_schema    = tc.table_schema
                JOIN information_schema.constraint_column_usage ccu
                  ON ccu.constraint_name = tc.constraint_name
                 AND ccu.table_schema    = tc.table_schema
                WHERE tc.table_name      = '{$table}'
                  AND tc.constraint_type = 'FOREIGN KEY'
                  AND kcu.column_name    = '{$column}'
                  AND ccu.table_name     = 'pieces'
              LOOP
                EXECUTE format('ALTER TABLE {$table} DROP CONSTRAINT %I', fk_name);
              END LOOP;
            END \$\$;
            SQL;
        $this->addSql($sql);
    }
}
