<?php

declare(strict_types=1);

namespace DoctrineMigrations;

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

/**
 * Migration pour la table audit_log_archive et la fonction d'archivage.
 *
 * @see Story 1.7 - T8: Archivage
 * @see NFR-C5: Duree conservation 5 ans actif, puis archive
 */
final class Version20260203100001 extends AbstractMigration
{
    public function getDescription(): string
    {
        return 'Create audit_log_archive table and archival function (Story 1.7)';
    }

    public function up(Schema $schema): void
    {
        // T8.1: Create archive table with same structure as audit_log
        $this->addSql(<<<'SQL'
            CREATE TABLE audit_log_archive (
                id UUID PRIMARY KEY,
                aggregate_type VARCHAR(255) NOT NULL,
                aggregate_id UUID,
                event_type VARCHAR(255) NOT NULL,
                payload JSONB NOT NULL,
                metadata JSONB NOT NULL,
                occurred_at TIMESTAMPTZ NOT NULL,
                sequence_number BIGINT NOT NULL,
                archived_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
            )
        SQL);

        // Indexes for archive queries (less aggressive than active table)
        // Note: No event_type index on archive - less frequent queries, trade space for insert speed
        $this->addSql("CREATE INDEX idx_audit_archive_tenant ON audit_log_archive((metadata->>'tenant_id'))");
        $this->addSql('CREATE INDEX idx_audit_archive_occurred ON audit_log_archive(occurred_at)');
        $this->addSql('CREATE INDEX idx_audit_archive_archived ON audit_log_archive(archived_at)');

        // Archive is also append-only (immutable)
        $this->addSql('CREATE RULE audit_archive_no_update AS ON UPDATE TO audit_log_archive DO INSTEAD NOTHING');
        $this->addSql('CREATE RULE audit_archive_no_delete AS ON DELETE TO audit_log_archive DO INSTEAD NOTHING');

        $this->addSql("COMMENT ON TABLE audit_log_archive IS 'Archived audit entries (>5 years old). Append-only, immutable. Entries are moved from audit_log, not copied.'");

        // T8.2-T8.3: Create SECURITY DEFINER function to bypass audit_no_delete rule
        // This is the ONLY way to delete from audit_log - for archival purposes only
        $this->addSql(<<<'SQL'
            CREATE OR REPLACE FUNCTION archive_audit_entries(
                p_cutoff_date TIMESTAMPTZ,
                p_batch_size INTEGER
            )
            RETURNS INTEGER
            LANGUAGE plpgsql
            SECURITY DEFINER
            SET search_path = public
            AS $$
            DECLARE
                v_archived_count INTEGER;
                v_ids UUID[];
            BEGIN
                -- Select IDs to archive (oldest first, up to batch_size)
                SELECT ARRAY_AGG(id ORDER BY sequence_number ASC)
                INTO v_ids
                FROM (
                    SELECT id, sequence_number
                    FROM audit_log
                    WHERE occurred_at < p_cutoff_date
                    ORDER BY sequence_number ASC
                    LIMIT p_batch_size
                ) sub;

                -- Nothing to archive
                IF v_ids IS NULL OR array_length(v_ids, 1) IS NULL THEN
                    RETURN 0;
                END IF;

                -- Insert into archive
                INSERT INTO audit_log_archive (
                    id, aggregate_type, aggregate_id, event_type,
                    payload, metadata, occurred_at, sequence_number
                )
                SELECT
                    id, aggregate_type, aggregate_id, event_type,
                    payload, metadata, occurred_at, sequence_number
                FROM audit_log
                WHERE id = ANY(v_ids);

                GET DIAGNOSTICS v_archived_count = ROW_COUNT;

                -- Delete from source table (bypasses rule via SECURITY DEFINER)
                -- We temporarily disable the rule for this session
                ALTER TABLE audit_log DISABLE RULE audit_no_delete;
                DELETE FROM audit_log WHERE id = ANY(v_ids);
                ALTER TABLE audit_log ENABLE RULE audit_no_delete;

                RETURN v_archived_count;
            END;
            $$
        SQL);

        $this->addSql("COMMENT ON FUNCTION archive_audit_entries IS 'Moves audit entries older than cutoff to archive table. Only method to delete from audit_log. Requires superuser or table owner.'");
    }

    public function down(Schema $schema): void
    {
        $this->addSql('DROP FUNCTION IF EXISTS archive_audit_entries');
        $this->addSql('DROP RULE IF EXISTS audit_archive_no_update ON audit_log_archive');
        $this->addSql('DROP RULE IF EXISTS audit_archive_no_delete ON audit_log_archive');
        $this->addSql('DROP TABLE IF EXISTS audit_log_archive');
    }
}
