<?php

declare(strict_types=1);

namespace DoctrineMigrations;

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

/**
 * M3 (ticket 1.1) — Taxonomie PRESTATAIRE (module Catalog, prerequis du module Technique).
 *
 * Contexte : le M3 (repertoire prestataires) a besoin d'une taxonomie distincte
 * des types CLIENT (M1) et FOURNISSEUR (M2). Decision Matthieu (11/06) : types
 * distincts CLIENT / FOURNISSEUR / PRESTATAIRE, chacun avec sa taxonomie. Le
 * multi-select « Categorie » du prestataire (formulaire principal + adresse)
 * ne reference que des `Category` rattachees au type PRESTATAIRE (RG-3.09).
 *
 * Cette migration :
 *  1. cree le `category_type` PRESTATAIRE (code PRESTATAIRE, label « Prestataire ») ;
 *  2. seede 3 `Category` de demonstration rattachees a ce type via la jonction
 *     ManyToMany `category_category_type` (modele courant depuis Version20260608120000 ;
 *     la colonne ManyToOne `category.category_type_id` n'existe plus).
 *
 * Aucune colonne creee/modifiee -> pas de `COMMENT ON COLUMN` (regle ABSOLUE n°12) :
 * la migration ne fait que des INSERT de donnees de reference.
 *
 * Namespace racine `DoctrineMigrations` (regle ABSOLUE n°11) et NON modulaire :
 * avec plusieurs migrations_paths, Doctrine Migrations 3.x trie par FQCN
 * alphabetique -> une migration `App\Module\...` passerait avant les
 * `DoctrineMigrations\...` sur base vide, donc avant la creation des tables
 * `category` / `category_type` / `category_category_type`. Le namespace racine
 * garantit l'ordre par timestamp.
 *
 * Idempotence : `INSERT ... ON CONFLICT (code) DO NOTHING` pour le type,
 * `INSERT ... SELECT ... WHERE NOT EXISTS` pour chaque categorie et chaque ligne
 * de jonction (aligne sur le pattern ERP-84 / Version20260605120000). En prod la
 * table `category` est vide (aucune fixture metier). En dev/test, le purger
 * Doctrine vide `category` / `category_type` avant les fixtures qui reproduisent
 * le meme etat final (CategoryTypeFixtures / CategoryFixtures etendus a PRESTATAIRE).
 */
final class Version20260612080000 extends AbstractMigration
{
    /**
     * Categories de demonstration du type PRESTATAIRE : nom => code stable. Le
     * code est la cle metier (slug MAJUSCULE du nom, miroir du
     * CategoryCodeGenerator) et reste unique parmi les actifs (uq_category_code,
     * partage avec les codes CLIENT / FOURNISSEUR — aucune collision ici). Le nom
     * est unique GLOBALEMENT parmi les actifs (uq_category_name_active) : les
     * libelles ci-dessous n'entrent en collision avec aucune categorie seedee.
     */
    private const array PROVIDER_CATEGORIES = [
        'Maintenance industrielle' => 'MAINTENANCE_INDUSTRIELLE',
        'Nettoyage'                => 'NETTOYAGE',
        'Transport'                => 'TRANSPORT',
    ];

    public function getDescription(): string
    {
        return 'M3 1.1 : cree le CategoryType PRESTATAIRE + seed des categories prestataires (Maintenance industrielle, Nettoyage, Transport).';
    }

    public function up(Schema $schema): void
    {
        // 1. Type PRESTATAIRE (idempotent via l'index unique uq_category_type_code).
        $this->addSql(<<<'SQL'
            INSERT INTO category_type (code, label) VALUES ('PRESTATAIRE', 'Prestataire')
            ON CONFLICT (code) DO NOTHING
            SQL);

        foreach (self::PROVIDER_CATEGORIES as $name => $code) {
            // 2a. Categorie sous PRESTATAIRE (si le code est libre parmi les
            //     actifs). created_at/updated_at NOT NULL -> NOW() ; le blame
            //     reste null (seed hors contexte HTTP, libelle « Systeme » cote front).
            $this->addSql(<<<'SQL'
                INSERT INTO category (name, code, created_at, updated_at)
                SELECT :name, :code, NOW(), NOW()
                WHERE NOT EXISTS (
                    SELECT 1 FROM category c WHERE c.code = :code AND c.deleted_at IS NULL
                )
                SQL, ['name' => $name, 'code' => $code]);

            // 2b. Jonction M2M categorie <-> type PRESTATAIRE (modele courant).
            $this->addSql(<<<'SQL'
                INSERT INTO category_category_type (category_id, category_type_id)
                SELECT c.id, ct.id
                FROM category c
                CROSS JOIN category_type ct
                WHERE c.code = :code AND c.deleted_at IS NULL
                  AND ct.code = 'PRESTATAIRE'
                  AND NOT EXISTS (
                      SELECT 1 FROM category_category_type cct
                      WHERE cct.category_id = c.id AND cct.category_type_id = ct.id
                  )
                SQL, ['code' => $code]);
        }
    }

    public function down(Schema $schema): void
    {
        // Best-effort : on retire d'abord les categories seedees (par code) — la FK
        // category_category_type est ON DELETE CASCADE cote category, donc les
        // lignes de jonction partent avec —, puis le type s'il n'est plus reference.
        $this->addSql(
            'DELETE FROM category WHERE code IN (:codes) '
            ."AND id IN (SELECT category_id FROM category_category_type cct "
            ."JOIN category_type ct ON ct.id = cct.category_type_id WHERE ct.code = 'PRESTATAIRE')",
            ['codes' => array_values(self::PROVIDER_CATEGORIES)],
            ['codes' => ArrayParameterType::STRING],
        );

        $this->addSql(<<<'SQL'
            DELETE FROM category_type
            WHERE code = 'PRESTATAIRE'
              AND NOT EXISTS (
                  SELECT 1 FROM category_category_type cct WHERE cct.category_type_id = category_type.id
              )
            SQL);
    }
}
