<?php

declare(strict_types=1);

namespace App\Tests\Architecture;

use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Connection;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

/**
 * Garde-fou architecture : toute colonne d'une table metier doit porter une
 * description SQL (`COMMENT ON COLUMN`).
 *
 * Postgres stocke la description dans `pg_description`, recuperable via
 * `col_description(table_oid, column_position)`. Une colonne sans description
 * remonte `NULL`. Le test parcourt `information_schema.columns` filtre sur le
 * schema `public` et echoue si une seule colonne metier n'a pas de description.
 *
 * Tables ignorees :
 * - `doctrine_migration_versions` : table system Doctrine, schema fige par la
 *   librairie.
 * - Whitelist `EXCLUDED_TABLES` : doit rester vide ou justifiee — toute entree
 *   doit avoir un ticket Lesstime ouvert pour le retrofit.
 *
 * @internal
 */
final class ColumnsHaveSqlCommentTest extends KernelTestCase
{
    /**
     * Tables system, gerees par Doctrine — leur schema n'est pas notre.
     */
    private const EXCLUDED_BUILTINS = [
        'doctrine_migration_versions',
    ];

    /**
     * Entites mappees uniquement en `when@test` (fixtures techniques pour les
     * tests d'integration, jamais en prod). Pas de migration, donc pas de
     * lieu naturel pour poser un COMMENT ON COLUMN.
     *
     * @var list<string>
     */
    private const EXCLUDED_TEST_FIXTURES = [
        // tests/Fixtures/SiteAware/FakeSiteAwareEntity.php — fixture du module
        // Sites pour couvrir le SiteScopedQueryExtension. Cree via schema:update
        // sur la DB de test uniquement.
        'fake_site_aware_entity',
    ];

    /**
     * Whitelist metier — DOIT rester vide ou justifiee.
     *
     * Chaque entree doit comporter (1) un commentaire expliquant pourquoi la
     * table n'est pas encore documentee et (2) la reference d'un ticket
     * Lesstime ouvert pour le retrofit.
     *
     * @var list<string>
     */
    private const EXCLUDED_TABLES = [];

    public function testAllPublicColumnsHaveASqlComment(): void
    {
        /** @var Connection $conn */
        $conn = self::getContainer()->get('doctrine.dbal.default_connection');

        $excluded = [...self::EXCLUDED_BUILTINS, ...self::EXCLUDED_TEST_FIXTURES, ...self::EXCLUDED_TABLES];

        $rows = $conn->fetchAllAssociative(
            <<<'SQL'
                SELECT c.table_name, c.column_name
                FROM information_schema.columns c
                WHERE c.table_schema = 'public'
                  AND c.table_name NOT IN (:excluded)
                  AND col_description(
                          (c.table_schema || '.' || c.table_name)::regclass,
                          c.ordinal_position
                      ) IS NULL
                ORDER BY c.table_name, c.ordinal_position
                SQL,
            ['excluded' => $excluded],
            ['excluded' => ArrayParameterType::STRING],
        );

        if ([] !== $rows) {
            $missing = array_map(
                static fn (array $row): string => sprintf('%s.%s', $row['table_name'], $row['column_name']),
                $rows,
            );

            self::fail(sprintf(
                "%d colonne(s) sans COMMENT ON COLUMN — ajouter une description SQL dans la migration qui les cree (cf. .claude/rules/backend.md § Migrations Doctrine) :\n  - %s",
                count($missing),
                implode("\n  - ", $missing),
            ));
        }

        // Garde : si la requete ne renvoie rien et qu'aucune table publique
        // n'existe (sauf doctrine_migration_versions), le test deviendrait un
        // faux positif vert. On verifie qu'il y a bien des tables a auditer.
        $tableCount = (int) $conn->fetchOne(
            <<<'SQL'
                SELECT COUNT(*)
                FROM information_schema.tables
                WHERE table_schema = 'public'
                  AND table_name NOT IN (:excluded)
                SQL,
            ['excluded' => $excluded],
            ['excluded' => ArrayParameterType::STRING],
        );

        self::assertGreaterThan(0, $tableCount, 'Aucune table publique a auditer : schema vide ou whitelist trop large.');
    }
}
