<?php

declare(strict_types=1);

namespace DoctrineMigrations;

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

/**
 * Migration RBAC - Task 5.
 *
 * Ce que fait cette migration :
 *  - Cree les tables du modele RBAC relationnel : permission, role,
 *    role_permission, user_role et user_permission.
 *  - Ajoute la colonne "user".is_admin (booleen, defaut false) qui sert de
 *    bypass complet pour les super-administrateurs.
 *  - Migre les donnees existantes depuis la colonne JSON "user".roles vers
 *    le nouveau modele relationnel : les users ayant ROLE_ADMIN passent a
 *    is_admin = TRUE et sont rattaches au role systeme 'admin', les autres
 *    sont rattaches au role systeme 'user'.
 *  - Supprime enfin la colonne "user".roles devenue obsolete.
 *
 * Ordonnancement critique :
 *  Les INSERT de data-migration DOIVENT s'executer AVANT le DROP COLUMN
 *  "user".roles, sinon l'information d'appartenance a ROLE_ADMIN est perdue.
 *  L'ordre respecte donc strictement : creation des tables -> ADD is_admin
 *  -> seed des roles systeme -> migration des donnees -> DROP roles.
 *
 * Dependance avec Task 6 (fixtures) :
 *  Les fixtures applicatives reposent sur l'existence des roles systeme
 *  'admin' et 'user' seedes ici par SQL brut. Cette migration est donc
 *  auto-suffisante et n'a pas besoin que les fixtures soient executees.
 */
final class Version20260414150034 extends AbstractMigration
{
    public function getDescription(): string
    {
        return 'RBAC : tables permission/role + jointures + is_admin + migration des donnees depuis user.roles';
    }

    public function up(Schema $schema): void
    {
        // 1) Creation des tables RBAC (permission, role, jointures).
        $this->addSql(<<<'SQL'
                CREATE TABLE permission (
                  id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
                  code VARCHAR(255) NOT NULL,
                  label VARCHAR(255) NOT NULL,
                  module VARCHAR(100) NOT NULL,
                  orphan BOOLEAN DEFAULT false NOT NULL,
                  PRIMARY KEY (id)
                )
            SQL);
        $this->addSql('CREATE INDEX idx_permission_module ON permission (module)');
        $this->addSql('CREATE INDEX idx_permission_orphan ON permission (orphan)');
        $this->addSql('CREATE UNIQUE INDEX uniq_permission_code ON permission (code)');
        $this->addSql(<<<'SQL'
                CREATE TABLE "role" (
                  id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
                  code VARCHAR(100) NOT NULL,
                  label VARCHAR(255) NOT NULL,
                  description TEXT DEFAULT NULL,
                  is_system BOOLEAN DEFAULT false NOT NULL,
                  PRIMARY KEY (id)
                )
            SQL);
        $this->addSql('CREATE INDEX idx_role_is_system ON "role" (is_system)');
        $this->addSql('CREATE UNIQUE INDEX uniq_role_code ON "role" (code)');
        $this->addSql(<<<'SQL'
                CREATE TABLE role_permission (
                  role_id INT NOT NULL,
                  permission_id INT NOT NULL,
                  PRIMARY KEY (role_id, permission_id)
                )
            SQL);
        $this->addSql('CREATE INDEX IDX_6F7DF886D60322AC ON role_permission (role_id)');
        $this->addSql('CREATE INDEX IDX_6F7DF886FED90CCA ON role_permission (permission_id)');
        $this->addSql(<<<'SQL'
                CREATE TABLE user_role (
                  user_id INT NOT NULL,
                  role_id INT NOT NULL,
                  PRIMARY KEY (user_id, role_id)
                )
            SQL);
        $this->addSql('CREATE INDEX IDX_2DE8C6A3A76ED395 ON user_role (user_id)');
        $this->addSql('CREATE INDEX IDX_2DE8C6A3D60322AC ON user_role (role_id)');
        $this->addSql(<<<'SQL'
                CREATE TABLE user_permission (
                  user_id INT NOT NULL,
                  permission_id INT NOT NULL,
                  PRIMARY KEY (user_id, permission_id)
                )
            SQL);
        $this->addSql('CREATE INDEX IDX_472E5446A76ED395 ON user_permission (user_id)');
        $this->addSql('CREATE INDEX IDX_472E5446FED90CCA ON user_permission (permission_id)');
        $this->addSql(<<<'SQL'
                ALTER TABLE
                  role_permission
                ADD
                  CONSTRAINT FK_6F7DF886D60322AC FOREIGN KEY (role_id) REFERENCES "role" (id) ON DELETE CASCADE
            SQL);
        $this->addSql(<<<'SQL'
                ALTER TABLE
                  role_permission
                ADD
                  CONSTRAINT FK_6F7DF886FED90CCA FOREIGN KEY (permission_id) REFERENCES permission (id) ON DELETE CASCADE
            SQL);
        $this->addSql(<<<'SQL'
                ALTER TABLE
                  user_role
                ADD
                  CONSTRAINT FK_2DE8C6A3A76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE
            SQL);
        $this->addSql(<<<'SQL'
                ALTER TABLE
                  user_role
                ADD
                  CONSTRAINT FK_2DE8C6A3D60322AC FOREIGN KEY (role_id) REFERENCES "role" (id) ON DELETE CASCADE
            SQL);
        $this->addSql(<<<'SQL'
                ALTER TABLE
                  user_permission
                ADD
                  CONSTRAINT FK_472E5446A76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE
            SQL);
        $this->addSql(<<<'SQL'
                ALTER TABLE
                  user_permission
                ADD
                  CONSTRAINT FK_472E5446FED90CCA FOREIGN KEY (permission_id) REFERENCES permission (id) ON DELETE CASCADE
            SQL);

        // 2) Ajout de la colonne is_admin sur "user" (avant la data-migration
        //    qui en a besoin pour marquer les super-admins).
        $this->addSql('ALTER TABLE "user" ADD is_admin BOOLEAN DEFAULT false NOT NULL');

        // 3) Seed des roles systeme avant toute migration de donnees utilisateurs.
        // Les codes sont centralises dans App\Module\Core\Domain\Security\SystemRoles
        // mais dupliques ici volontairement pour que la migration reste auto-suffisante.
        $this->addSql("INSERT INTO \"role\" (code, label, description, is_system) VALUES ('admin', 'Administrateur', 'Role administrateur - bypass complet via is_admin', TRUE)");
        $this->addSql("INSERT INTO \"role\" (code, label, description, is_system) VALUES ('user', 'Utilisateur', 'Role de base sans permission specifique', TRUE)");

        // 4) Bascule is_admin a TRUE pour tout user dont le JSON roles contient ROLE_ADMIN.
        $this->addSql(<<<'SQL'
                UPDATE "user" u
                SET is_admin = TRUE
                WHERE EXISTS (
                    SELECT 1
                    FROM jsonb_array_elements_text(COALESCE(u.roles::jsonb, '[]'::jsonb)) AS role_code
                    WHERE role_code = 'ROLE_ADMIN'
                )
            SQL);

        // 5) Rattachement des admins au role systeme 'admin'.
        $this->addSql(<<<'SQL'
                INSERT INTO user_role (user_id, role_id)
                SELECT u.id, r.id
                FROM "user" u
                CROSS JOIN "role" r
                WHERE r.code = 'admin'
                  AND EXISTS (
                      SELECT 1
                      FROM jsonb_array_elements_text(COALESCE(u.roles::jsonb, '[]'::jsonb)) AS role_code
                      WHERE role_code = 'ROLE_ADMIN'
                  )
                ON CONFLICT DO NOTHING
            SQL);

        // 6) Rattachement des autres users (y compris roles vide ou NULL) au role 'user'.
        $this->addSql(<<<'SQL'
                INSERT INTO user_role (user_id, role_id)
                SELECT u.id, r.id
                FROM "user" u
                CROSS JOIN "role" r
                WHERE r.code = 'user'
                  AND NOT EXISTS (
                      SELECT 1
                      FROM jsonb_array_elements_text(COALESCE(u.roles::jsonb, '[]'::jsonb)) AS role_code
                      WHERE role_code = 'ROLE_ADMIN'
                  )
                ON CONFLICT DO NOTHING
            SQL);

        // 7) Drop de la colonne "user".roles (DOIT etre la derniere instruction,
        //    apres la migration des donnees qui la lit).
        $this->addSql('ALTER TABLE "user" DROP roles');
    }

    public function down(Schema $schema): void
    {
        // 1) Recreation de la colonne roles (avec un defaut pour permettre le
        //    backfill). Le NOT NULL est conserve comme dans le schema d'origine.
        $this->addSql('ALTER TABLE "user" ADD roles JSON NOT NULL DEFAULT \'[]\'');

        // 2) Rehydratation du JSON roles depuis is_admin avant de perdre la colonne.
        $this->addSql(<<<'SQL'
                UPDATE "user"
                SET roles = CASE
                    WHEN is_admin THEN '["ROLE_ADMIN"]'::json
                    ELSE '["ROLE_USER"]'::json
                END
            SQL);

        // 3) Drop des FK puis des tables de jointure (enfants d'abord).
        $this->addSql('ALTER TABLE role_permission DROP CONSTRAINT FK_6F7DF886D60322AC');
        $this->addSql('ALTER TABLE role_permission DROP CONSTRAINT FK_6F7DF886FED90CCA');
        $this->addSql('ALTER TABLE user_role DROP CONSTRAINT FK_2DE8C6A3A76ED395');
        $this->addSql('ALTER TABLE user_role DROP CONSTRAINT FK_2DE8C6A3D60322AC');
        $this->addSql('ALTER TABLE user_permission DROP CONSTRAINT FK_472E5446A76ED395');
        $this->addSql('ALTER TABLE user_permission DROP CONSTRAINT FK_472E5446FED90CCA');
        $this->addSql('DROP TABLE user_permission');
        $this->addSql('DROP TABLE user_role');
        $this->addSql('DROP TABLE role_permission');

        // 4) Drop des tables parentes permission et role.
        $this->addSql('DROP TABLE permission');
        $this->addSql('DROP TABLE "role"');

        // 5) Drop de is_admin, la colonne roles ayant ete rehydratee en amont.
        $this->addSql('ALTER TABLE "user" DROP is_admin');
    }
}
