<?php

declare(strict_types=1);

namespace DoctrineMigrations;

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

/**
 * Catalog — Category multi-types : passage de la relation Category -> CategoryType
 * de ManyToOne a ManyToMany.
 *
 * Ordre critique :
 *  1. Creation de la table de jonction `category_category_type` (FK category ON
 *     DELETE CASCADE, FK category_type ON DELETE RESTRICT — conserve le garde-fou
 *     « on ne supprime pas un type encore reference »).
 *  2. Backfill : chaque categorie existante recoit une ligne de jonction vers son
 *     ancien `category_type_id` (avant de dropper la colonne).
 *  3. Drop de l'index unique (LOWER(name), category_type_id), de l'index FK et de
 *     la colonne `category.category_type_id` (Postgres drope la FK dependante).
 *  4. Nouvel index unique GLOBAL sur le nom : LOWER(name) WHERE deleted_at IS NULL
 *     (l'unicite n'est plus liee au type — RG-1.07 reformulee).
 *
 * Sur base fraiche, les categories seedees CLIENT (Distributeur/Courtier/Secteur/
 * Autre) et FOURNISSEUR (Negociant/Cooperative/...) n'ont aucun nom en collision
 * -> l'index unique global passe sans conflit.
 *
 * Migration placee au namespace racine `DoctrineMigrations` (regle ABSOLUE n°11) :
 * Doctrine Migrations 3.x trie par FQCN puis version ; le namespace racine garantit
 * l'ordre par timestamp apres les migrations d'init des tables.
 */
final class Version20260608120000 extends AbstractMigration
{
    public function getDescription(): string
    {
        return 'Catalog : Category <-> CategoryType en ManyToMany (jonction category_category_type), unicite du nom globalisee.';
    }

    public function up(Schema $schema): void
    {
        // 1. Table de jonction.
        $this->addSql(<<<'SQL'
            CREATE TABLE category_category_type (
                category_id INT NOT NULL,
                category_type_id INT NOT NULL,
                PRIMARY KEY (category_id, category_type_id),
                CONSTRAINT fk_category_category_type_category
                    FOREIGN KEY (category_id) REFERENCES category (id) ON DELETE CASCADE,
                CONSTRAINT fk_category_category_type_type
                    FOREIGN KEY (category_type_id) REFERENCES category_type (id) ON DELETE RESTRICT
            )
            SQL);
        $this->addSql('CREATE INDEX idx_cat_cat_type_type ON category_category_type (category_type_id)');

        $this->comment('category_category_type', '_table', 'Jointure M2M category <-> category_type (Catalog) — types portes par la categorie, au moins un obligatoire (RG-1.05).');
        $this->comment('category_category_type', 'category_id', 'FK -> category.id, ON DELETE CASCADE — categorie portant le type.');
        $this->comment('category_category_type', 'category_type_id', 'FK -> category_type.id, ON DELETE RESTRICT — type rattache (un type ne peut etre supprime tant qu il reste reference).');

        // 2. Backfill depuis l'ancienne colonne ManyToOne (chaque categorie -> 1 type).
        $this->addSql(<<<'SQL'
            INSERT INTO category_category_type (category_id, category_type_id)
            SELECT id, category_type_id FROM category
            SQL);

        // 3. Suppression de l'ancien modele : index unique par type, index FK, colonne.
        $this->addSql('DROP INDEX uq_category_name_type_active');
        $this->addSql('DROP INDEX idx_category_type_id');
        // DROP COLUMN drope automatiquement la FK fk_category_type qui en depend.
        $this->addSql('ALTER TABLE category DROP COLUMN category_type_id');

        // 4. Unicite du nom desormais GLOBALE parmi les actifs (RG-1.07 reformulee).
        $this->addSql(<<<'SQL'
            CREATE UNIQUE INDEX uq_category_name_active
            ON category (LOWER(name))
            WHERE deleted_at IS NULL
            SQL);

        // Realignement de la doc SQL de `category` (le type n'est plus une colonne).
        $this->comment('category', '_table', 'Categories — referentiel multi-types via la jonction category_category_type, soft-delete via deleted_at, unicite LOWER(name) GLOBALE parmi les actifs (uq_category_name_active).');
        $this->comment('category', 'name', 'Libelle de la categorie (≤ 120 caracteres) — unique GLOBALEMENT parmi les actifs (RG-1.07, uq_category_name_active).');
    }

    public function down(Schema $schema): void
    {
        // Restauration best-effort de l'ancien modele ManyToOne (1 type par categorie).
        $this->addSql('DROP INDEX IF EXISTS uq_category_name_active');

        $this->addSql('ALTER TABLE category ADD COLUMN category_type_id INT DEFAULT NULL');

        // Reprend le premier type de chaque categorie (l'ordre des types perdus
        // au-dela du premier est best-effort : le modele cible n'en gardait qu'un).
        $this->addSql(<<<'SQL'
            UPDATE category c
            SET category_type_id = (
                SELECT cct.category_type_id
                FROM category_category_type cct
                WHERE cct.category_id = c.id
                ORDER BY cct.category_type_id ASC
                LIMIT 1
            )
            SQL);

        // Categories sans aucun type (theorique) : on les rattache a defaut au
        // premier type existant pour pouvoir reposer le NOT NULL.
        $this->addSql(<<<'SQL'
            UPDATE category
            SET category_type_id = (SELECT id FROM category_type ORDER BY id ASC LIMIT 1)
            WHERE category_type_id IS NULL
            SQL);

        $this->addSql('ALTER TABLE category ALTER COLUMN category_type_id SET NOT NULL');
        $this->addSql(<<<'SQL'
            ALTER TABLE category
            ADD CONSTRAINT fk_category_type
                FOREIGN KEY (category_type_id) REFERENCES category_type (id) ON DELETE RESTRICT
            SQL);
        $this->addSql('CREATE INDEX idx_category_type_id ON category (category_type_id)');
        $this->addSql(<<<'SQL'
            CREATE UNIQUE INDEX uq_category_name_type_active
            ON category (LOWER(name), category_type_id)
            WHERE deleted_at IS NULL
            SQL);

        $this->addSql('DROP TABLE category_category_type');
    }

    /**
     * Emet un `COMMENT ON TABLE` (colonne speciale `_table`) ou `COMMENT ON COLUMN`
     * en dollar-quoting Postgres ($_$...$_$) pour eviter tout echappement.
     */
    private function comment(string $table, string $column, string $description): void
    {
        $quotedTable = '"'.str_replace('"', '""', $table).'"';

        if ('_table' === $column) {
            $this->addSql(sprintf('COMMENT ON TABLE %s IS $_$%s$_$', $quotedTable, $description));

            return;
        }

        $this->addSql(sprintf(
            'COMMENT ON COLUMN %s.%s IS $_$%s$_$',
            $quotedTable,
            '"'.str_replace('"', '""', $column).'"',
            $description,
        ));
    }
}
