"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MigrationManager = void 0;
const uuid_1 = require("uuid");
class MigrationManager {
    db;
    constructor(db) {
        this.db = db;
        this.initializeMigrationTables();
    }
    initializeMigrationTables() {
        this.db.getDatabase().exec(`
      CREATE TABLE IF NOT EXISTS migrations (
        id TEXT PRIMARY KEY,
        version TEXT UNIQUE NOT NULL,
        name TEXT NOT NULL,
        description TEXT,
        up_sql TEXT NOT NULL,
        down_sql TEXT,
        dependencies TEXT, -- JSON array
        requires_backup BOOLEAN DEFAULT false,
        checksum TEXT,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        applied_at TIMESTAMP,
        rollback_at TIMESTAMP
      );
      
      CREATE TABLE IF NOT EXISTS migration_log (
        id TEXT PRIMARY KEY,
        migration_id TEXT NOT NULL,
        version TEXT NOT NULL,
        action TEXT NOT NULL, -- 'apply', 'rollback', 'backup'
        success BOOLEAN NOT NULL,
        errors TEXT, -- JSON array
        warnings TEXT, -- JSON array
        execution_time INTEGER, -- milliseconds
        rows_affected INTEGER,
        backup_path TEXT,
        timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (migration_id) REFERENCES migrations(id)
      );
      
      CREATE INDEX IF NOT EXISTS idx_migrations_version ON migrations(version);
      CREATE INDEX IF NOT EXISTS idx_migrations_applied ON migrations(applied_at);
      CREATE INDEX IF NOT EXISTS idx_migration_log_version ON migration_log(version);
      CREATE INDEX IF NOT EXISTS idx_migration_log_timestamp ON migration_log(timestamp);
    `);
    }
    createMigration(migration) {
        const id = (0, uuid_1.v4)();
        const now = new Date().toISOString();
        // Calculate checksum
        const checksum = this.calculateChecksum(migration.up + (migration.down || ''));
        const _migrationWithDefaults = {
            id,
            createdAt: now,
            checksum,
            requiresBackup: false,
            ...migration,
        };
        this.db
            .getDatabase()
            .prepare(`
      INSERT INTO migrations (
        id, version, name, description, up_sql, down_sql, dependencies,
        requires_backup, checksum, created_at
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `)
            .run(id, migration.version, migration.name, migration.description, migration.up, migration.down, migration.dependencies ? JSON.stringify(migration.dependencies) : null, migration.requiresBackup ? 1 : 0, checksum, now);
        return id;
    }
    getMigration(version) {
        const row = this.db
            .getDatabase()
            .prepare(`
      SELECT * FROM migrations WHERE version = ?
    `)
            .get(version);
        if (!row)
            return null;
        return this.rowToMigration(row);
    }
    listMigrations(options = {}) {
        let query = 'SELECT * FROM migrations WHERE 1=1';
        const params = [];
        if (options.applied === true) {
            query += ' AND applied_at IS NOT NULL';
        }
        else if (options.applied === false) {
            query += ' AND applied_at IS NULL';
        }
        if (options.pending === true) {
            query += ' AND applied_at IS NULL';
        }
        else if (options.pending === false) {
            query += ' AND applied_at IS NOT NULL';
        }
        query += ' ORDER BY version';
        if (options.limit) {
            query += ' LIMIT ?';
            params.push(options.limit);
        }
        const rows = this.db
            .getDatabase()
            .prepare(query)
            .all(...params);
        return rows.map(row => this.rowToMigration(row));
    }
    getStatus() {
        const allMigrations = this.listMigrations();
        const appliedMigrations = allMigrations.filter(m => m.appliedAt);
        const pendingMigrations = allMigrations.filter(m => !m.appliedAt);
        const lastMigration = appliedMigrations.sort((a, b) => (b.appliedAt || '').localeCompare(a.appliedAt || ''))[0];
        return {
            currentVersion: lastMigration?.version || '0.0.0',
            totalMigrations: allMigrations.length,
            appliedMigrations: appliedMigrations.length,
            pendingMigrations: pendingMigrations.length,
            pending: pendingMigrations.map(m => ({
                version: m.version,
                name: m.name,
                requiresBackup: m.requiresBackup || false,
            })),
            applied: appliedMigrations.map(m => ({
                version: m.version,
                name: m.name,
                appliedAt: m.appliedAt,
            })),
            lastMigration: lastMigration
                ? {
                    version: lastMigration.version,
                    name: lastMigration.name,
                    appliedAt: lastMigration.appliedAt,
                }
                : undefined,
        };
    }
    async applyMigration(version, options = {}) {
        const startTime = Date.now();
        const migration = this.getMigration(version);
        if (!migration) {
            throw new Error(`Migration not found: ${version}`);
        }
        if (migration.appliedAt) {
            throw new Error(`Migration ${version} is already applied`);
        }
        const result = {
            migrationId: migration.id,
            version: migration.version,
            name: migration.name,
            success: false,
            errors: [],
            warnings: [],
            executionTime: 0,
            timestamp: new Date().toISOString(),
        };
        try {
            // Check dependencies
            if (migration.dependencies) {
                for (const depVersion of migration.dependencies) {
                    const dep = this.getMigration(depVersion);
                    if (!dep || !dep.appliedAt) {
                        result.errors.push(`Dependency not satisfied: ${depVersion}`);
                        result.success = false;
                        result.executionTime = Date.now() - startTime;
                        this.logMigration(result, 'apply', result.backupCreated);
                        return result;
                    }
                }
            }
            // Validate migration SQL
            try {
                this.validateSQL(migration.up);
            }
            catch (error) {
                result.errors.push(error.message);
                result.success = false;
                result.executionTime = Date.now() - startTime;
                this.logMigration(result, 'apply', result.backupCreated);
                return result;
            }
            // Create backup if required or requested
            let backupPath;
            if ((migration.requiresBackup || options.createBackup) && !options.dryRun) {
                backupPath = await this.createBackup(version);
                result.backupCreated = backupPath;
            }
            if (!options.dryRun) {
                // Execute migration
                const db = this.db.getDatabase();
                // Begin transaction
                db.exec('BEGIN TRANSACTION');
                try {
                    // Execute the migration SQL
                    db.exec(migration.up);
                    result.rowsAffected = db.changes || 0;
                    // Mark migration as applied
                    db.prepare(`
            UPDATE migrations 
            SET applied_at = CURRENT_TIMESTAMP 
            WHERE id = ?
          `).run(migration.id);
                    // Commit transaction
                    db.exec('COMMIT');
                    result.success = true;
                }
                catch (error) {
                    // Rollback transaction
                    db.exec('ROLLBACK');
                    throw error;
                }
            }
            else {
                result.success = true;
                result.warnings.push('Dry run - no changes applied');
            }
        }
        catch (error) {
            result.errors.push(error.message);
            result.success = false;
        }
        result.executionTime = Date.now() - startTime;
        // Log the result
        this.logMigration(result, 'apply', result.backupCreated);
        return result;
    }
    async rollbackMigration(version, options = {}) {
        const startTime = Date.now();
        const migration = this.getMigration(version);
        if (!migration) {
            throw new Error(`Migration not found: ${version}`);
        }
        if (!migration.appliedAt) {
            throw new Error(`Migration ${version} is not applied`);
        }
        if (!migration.down) {
            throw new Error(`Migration ${version} has no rollback SQL`);
        }
        const result = {
            migrationId: migration.id,
            version: migration.version,
            name: migration.name,
            success: false,
            errors: [],
            warnings: [],
            executionTime: 0,
            timestamp: new Date().toISOString(),
        };
        try {
            // Create backup if requested
            let backupPath;
            if (options.createBackup && !options.dryRun) {
                backupPath = await this.createBackup(version);
                result.backupCreated = backupPath;
            }
            // Validate rollback SQL
            this.validateSQL(migration.down);
            if (!options.dryRun) {
                // Execute rollback
                const db = this.db.getDatabase();
                // Begin transaction
                db.exec('BEGIN TRANSACTION');
                try {
                    // Execute the rollback SQL
                    db.exec(migration.down);
                    result.rowsAffected = db.changes || 0;
                    // Mark migration as rolled back
                    db.prepare(`
            UPDATE migrations 
            SET applied_at = NULL, rollback_at = CURRENT_TIMESTAMP 
            WHERE id = ?
          `).run(migration.id);
                    // Commit transaction
                    db.exec('COMMIT');
                    result.success = true;
                }
                catch (error) {
                    // Rollback transaction
                    db.exec('ROLLBACK');
                    throw error;
                }
            }
            else {
                result.success = true;
                result.warnings.push('Dry run - no changes applied');
            }
        }
        catch (error) {
            result.errors.push(error.message);
            result.success = false;
        }
        result.executionTime = Date.now() - startTime;
        // Log the result
        this.logMigration(result, 'rollback', result.backupCreated);
        return result;
    }
    async applyAllPending(options = {}) {
        const pendingMigrations = this.listMigrations({ pending: true });
        const results = [];
        for (const migration of pendingMigrations) {
            try {
                const result = await this.applyMigration(migration.version, {
                    dryRun: options.dryRun,
                    createBackup: options.createBackups,
                });
                results.push(result);
                if (!result.success && options.stopOnError) {
                    break;
                }
            }
            catch (error) {
                const errorResult = {
                    migrationId: migration.id,
                    version: migration.version,
                    name: migration.name,
                    success: false,
                    errors: [error.message],
                    warnings: [],
                    executionTime: 0,
                    timestamp: new Date().toISOString(),
                };
                results.push(errorResult);
                if (options.stopOnError) {
                    break;
                }
            }
        }
        return results;
    }
    getMigrationLog(version, limit = 50) {
        const query = version
            ? 'SELECT * FROM migration_log WHERE version = ? ORDER BY timestamp DESC LIMIT ?'
            : 'SELECT * FROM migration_log ORDER BY timestamp DESC LIMIT ?';
        const params = version ? [version, limit] : [limit];
        return this.db
            .getDatabase()
            .prepare(query)
            .all(...params);
    }
    async createBackup(version) {
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        const backupPath = `context_backup_${version}_${timestamp}.db`;
        // For SQLite, we can create a backup by copying the database file
        // In a more complex implementation, you might use SQLite backup API
        const fs = require('fs');
        fs.copyFileSync(this.db.getDatabase().name, backupPath);
        return backupPath;
    }
    validateSQL(sql) {
        // Basic SQL validation
        if (!sql || sql.trim().length === 0) {
            throw new Error('Empty SQL statement');
        }
        // Check for potentially dangerous operations
        const dangerousPatterns = [
            /DROP\s+DATABASE/i,
            /DELETE\s+FROM\s+\w+\s*;?\s*$/i, // DELETE without WHERE
            /TRUNCATE/i,
        ];
        for (const pattern of dangerousPatterns) {
            if (pattern.test(sql)) {
                throw new Error('Potentially dangerous SQL detected');
            }
        }
    }
    calculateChecksum(content) {
        const crypto = require('crypto');
        return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
    }
    rowToMigration(row) {
        return {
            id: row.id,
            version: row.version,
            name: row.name,
            description: row.description,
            up: row.up_sql,
            down: row.down_sql,
            dependencies: row.dependencies ? JSON.parse(row.dependencies) : undefined,
            requiresBackup: Boolean(row.requires_backup),
            checksum: row.checksum,
            createdAt: row.created_at,
            appliedAt: row.applied_at,
            rollbackAt: row.rollback_at,
        };
    }
    logMigration(result, action, backupPath) {
        this.db
            .getDatabase()
            .prepare(`
      INSERT INTO migration_log (
        id, migration_id, version, action, success, errors, warnings,
        execution_time, rows_affected, backup_path
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `)
            .run((0, uuid_1.v4)(), result.migrationId, result.version, action, result.success ? 1 : 0, result.errors.length > 0 ? JSON.stringify(result.errors) : null, result.warnings.length > 0 ? JSON.stringify(result.warnings) : null, result.executionTime, result.rowsAffected, backupPath);
    }
    // Predefined migrations for common schema updates
    static getDefaultMigrations() {
        return [
            {
                version: '1.0.0',
                name: 'Add size column to context_items',
                description: 'Add size tracking for context items',
                up: `
          ALTER TABLE context_items ADD COLUMN size INTEGER DEFAULT 0;
          UPDATE context_items SET size = LENGTH(value) WHERE size = 0;
        `,
                down: `
          ALTER TABLE context_items DROP COLUMN size;
        `,
            },
            {
                version: '1.1.0',
                name: 'Create compressed_context table',
                description: 'Support for context compression',
                up: `
          CREATE TABLE IF NOT EXISTS compressed_context (
            id TEXT PRIMARY KEY,
            session_id TEXT NOT NULL,
            original_count INTEGER NOT NULL,
            compressed_data TEXT NOT NULL,
            compression_ratio REAL NOT NULL,
            date_range_start TIMESTAMP NOT NULL,
            date_range_end TIMESTAMP NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (session_id) REFERENCES sessions(id)
          );
          CREATE INDEX IF NOT EXISTS idx_compressed_session ON compressed_context(session_id);
        `,
                down: `
          DROP TABLE IF EXISTS compressed_context;
        `,
                dependencies: ['1.0.0'],
            },
            {
                version: '1.2.0',
                name: 'Add metadata support',
                description: 'Add metadata column for extensibility',
                up: `
          ALTER TABLE context_items ADD COLUMN metadata TEXT;
          ALTER TABLE sessions ADD COLUMN metadata TEXT;
        `,
                down: `
          ALTER TABLE context_items DROP COLUMN metadata;
          ALTER TABLE sessions DROP COLUMN metadata;
        `,
                dependencies: ['1.1.0'],
            },
        ];
    }
}
exports.MigrationManager = MigrationManager;
