"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FeatureFlagManager = void 0;
const uuid_1 = require("uuid");
class FeatureFlagManager {
    db;
    constructor(db) {
        this.db = db;
        this.initializeTables();
    }
    initializeTables() {
        this.db.getDatabase().exec(`
      CREATE TABLE IF NOT EXISTS feature_flags (
        id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        key TEXT UNIQUE NOT NULL,
        enabled BOOLEAN DEFAULT false,
        description TEXT,
        environments TEXT, -- JSON array
        users TEXT, -- JSON array
        percentage INTEGER DEFAULT 0,
        enabled_from TIMESTAMP,
        enabled_until TIMESTAMP,
        category TEXT,
        tags TEXT, -- JSON array
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        created_by TEXT,
        last_modified_by TEXT
      );
      
      CREATE TABLE IF NOT EXISTS feature_flag_evaluations (
        id TEXT PRIMARY KEY,
        flag_id TEXT NOT NULL,
        flag_key TEXT NOT NULL,
        enabled BOOLEAN NOT NULL,
        reason TEXT NOT NULL,
        context TEXT, -- JSON
        evaluated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (flag_id) REFERENCES feature_flags(id)
      );
      
      CREATE TABLE IF NOT EXISTS feature_flag_audit (
        id TEXT PRIMARY KEY,
        flag_id TEXT NOT NULL,
        flag_key TEXT NOT NULL,
        action TEXT NOT NULL, -- 'created', 'updated', 'deleted', 'enabled', 'disabled'
        old_value TEXT, -- JSON
        new_value TEXT, -- JSON
        user_id TEXT,
        timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
      );
      
      CREATE INDEX IF NOT EXISTS idx_feature_flags_key ON feature_flags(key);
      CREATE INDEX IF NOT EXISTS idx_feature_flags_enabled ON feature_flags(enabled);
      CREATE INDEX IF NOT EXISTS idx_feature_flags_category ON feature_flags(category);
      CREATE INDEX IF NOT EXISTS idx_evaluations_flag ON feature_flag_evaluations(flag_id);
      CREATE INDEX IF NOT EXISTS idx_evaluations_time ON feature_flag_evaluations(evaluated_at);
      CREATE INDEX IF NOT EXISTS idx_audit_flag ON feature_flag_audit(flag_id);
      CREATE INDEX IF NOT EXISTS idx_audit_time ON feature_flag_audit(timestamp);
    `);
    }
    createFlag(flag) {
        const id = (0, uuid_1.v4)();
        const now = new Date().toISOString();
        const flagWithDefaults = {
            id,
            createdAt: now,
            updatedAt: now,
            ...flag,
            enabled: flag.enabled ?? false,
            percentage: flag.percentage ?? undefined,
        };
        this.db
            .getDatabase()
            .prepare(`
      INSERT INTO feature_flags (
        id, name, key, enabled, description, environments, users, percentage,
        enabled_from, enabled_until, category, tags, created_at, updated_at,
        created_by, last_modified_by
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `)
            .run(id, flag.name, flag.key, flag.enabled ? 1 : 0, flag.description, flag.environments ? JSON.stringify(flag.environments) : null, flag.users ? JSON.stringify(flag.users) : null, flag.percentage !== undefined ? flag.percentage : null, flag.enabledFrom, flag.enabledUntil, flag.category, flag.tags ? JSON.stringify(flag.tags) : null, now, now, flag.createdBy, flag.lastModifiedBy);
        // Log creation
        this.logAudit(id, flag.key, 'created', null, flagWithDefaults, flag.createdBy);
        return id;
    }
    updateFlag(id, updates, userId) {
        const existing = this.getFlag(id);
        if (!existing) {
            throw new Error(`Feature flag not found: ${id}`);
        }
        const updated = { ...existing, ...updates, id, updatedAt: new Date().toISOString() };
        if (userId) {
            updated.lastModifiedBy = userId;
        }
        this.db
            .getDatabase()
            .prepare(`
      UPDATE feature_flags SET
        name = ?, enabled = ?, description = ?, environments = ?, users = ?,
        percentage = ?, enabled_from = ?, enabled_until = ?, category = ?,
        tags = ?, updated_at = ?, last_modified_by = ?
      WHERE id = ?
    `)
            .run(updated.name, updated.enabled ? 1 : 0, updated.description, updated.environments ? JSON.stringify(updated.environments) : null, updated.users ? JSON.stringify(updated.users) : null, updated.percentage !== undefined ? updated.percentage : null, updated.enabledFrom, updated.enabledUntil, updated.category, updated.tags ? JSON.stringify(updated.tags) : null, updated.updatedAt, updated.lastModifiedBy, id);
        // Log update
        this.logAudit(id, existing.key, 'updated', existing, updated, userId);
    }
    getFlag(id) {
        const row = this.db
            .getDatabase()
            .prepare(`
      SELECT * FROM feature_flags WHERE id = ?
    `)
            .get(id);
        if (!row)
            return null;
        return this.rowToFlag(row);
    }
    getFlagByKey(key) {
        const row = this.db
            .getDatabase()
            .prepare(`
      SELECT * FROM feature_flags WHERE key = ?
    `)
            .get(key);
        if (!row)
            return null;
        return this.rowToFlag(row);
    }
    listFlags(options = {}) {
        let query = 'SELECT * FROM feature_flags WHERE 1=1';
        const params = [];
        if (options.category) {
            query += ' AND category = ?';
            params.push(options.category);
        }
        if (options.enabled !== undefined) {
            query += ' AND enabled = ?';
            params.push(options.enabled ? 1 : 0);
        }
        if (options.environment) {
            query += ' AND (environments IS NULL OR environments LIKE ?)';
            params.push(`%"${options.environment}"%`);
        }
        if (options.tag) {
            query += ' AND tags LIKE ?';
            params.push(`%"${options.tag}"%`);
        }
        query += ' ORDER BY updated_at DESC';
        if (options.limit) {
            query += ' LIMIT ?';
            params.push(options.limit);
        }
        const rows = this.db
            .getDatabase()
            .prepare(query)
            .all(...params);
        return rows.map(row => this.rowToFlag(row));
    }
    deleteFlag(id, userId) {
        const existing = this.getFlag(id);
        if (!existing) {
            throw new Error(`Feature flag not found: ${id}`);
        }
        // Delete related evaluation records first to avoid foreign key constraints
        this.db
            .getDatabase()
            .prepare(`
      DELETE FROM feature_flag_evaluations WHERE flag_id = ?
    `)
            .run(id);
        // Now delete the flag (audit log can stay for historical purposes)
        this.db
            .getDatabase()
            .prepare(`
      DELETE FROM feature_flags WHERE id = ?
    `)
            .run(id);
        // Log deletion after removing the flag
        this.logAudit(id, existing.key, 'deleted', existing, null, userId);
    }
    evaluateFlag(key, context = {}) {
        const flag = this.getFlagByKey(key);
        const timestamp = context.timestamp || new Date().toISOString();
        if (!flag) {
            const evaluation = {
                flag: {
                    id: '',
                    name: key,
                    key,
                    enabled: false,
                    createdAt: timestamp,
                    updatedAt: timestamp,
                },
                enabled: false,
                reason: 'Flag not found',
                context: { ...context, timestamp },
            };
            this.logEvaluation('', key, false, 'Flag not found', evaluation.context);
            return evaluation;
        }
        let enabled = false;
        let reason = 'Flag disabled';
        // Check if flag is globally enabled
        if (!flag.enabled) {
            enabled = false;
            reason = 'Flag globally disabled';
        }
        // Check date constraints
        else if (flag.enabledFrom && new Date(timestamp) < new Date(flag.enabledFrom)) {
            enabled = false;
            reason = `Flag not yet active (starts ${flag.enabledFrom})`;
        }
        else if (flag.enabledUntil && new Date(timestamp) > new Date(flag.enabledUntil)) {
            enabled = false;
            reason = `Flag expired (ended ${flag.enabledUntil})`;
        }
        // Check environment constraints
        else if (flag.environments && flag.environments.length > 0 && context.environment) {
            if (flag.environments.includes(context.environment)) {
                enabled = true;
                reason = `Enabled for environment: ${context.environment}`;
            }
            else {
                enabled = false;
                reason = `Not enabled for environment: ${context.environment}`;
            }
        }
        // Check user constraints
        else if (flag.users && flag.users.length > 0 && context.userId) {
            if (flag.users.includes(context.userId)) {
                enabled = true;
                reason = `Enabled for user: ${context.userId}`;
            }
            else {
                enabled = false;
                reason = `Not enabled for user: ${context.userId}`;
            }
        }
        // Check percentage rollout
        else if (flag.percentage !== undefined && flag.percentage !== null) {
            if (flag.percentage === 0) {
                enabled = false;
                reason = `Disabled by percentage rollout (${flag.percentage}%)`;
            }
            else if (flag.percentage === 100) {
                enabled = true;
                reason = `Enabled by percentage rollout (${flag.percentage}%)`;
            }
            else {
                // Use hash of key + userId/environment for consistent percentage evaluation
                const hashInput = key + (context.userId || context.environment || 'anonymous');
                const hash = this.simpleHash(hashInput);
                const userPercentile = hash % 100;
                if (userPercentile < flag.percentage) {
                    enabled = true;
                    reason = `Enabled by percentage rollout (${flag.percentage}%)`;
                }
                else {
                    enabled = false;
                    reason = `Disabled by percentage rollout (${flag.percentage}%, user at ${userPercentile}%)`;
                }
            }
        }
        // Default to flag enabled state
        else {
            enabled = flag.enabled;
            reason = enabled ? 'Flag enabled' : 'Flag disabled';
        }
        const evaluation = {
            flag,
            enabled,
            reason,
            context: { ...context, timestamp },
        };
        // Log evaluation
        this.logEvaluation(flag.id, key, enabled, reason, evaluation.context);
        return evaluation;
    }
    isEnabled(key, context = {}) {
        return this.evaluateFlag(key, context).enabled;
    }
    getStats() {
        const flags = this.listFlags();
        const enabledFlags = flags.filter(f => f.enabled);
        // By category
        const byCategory = {};
        flags.forEach(flag => {
            const category = flag.category || 'uncategorized';
            if (!byCategory[category]) {
                byCategory[category] = { count: 0, enabled: 0 };
            }
            byCategory[category].count++;
            if (flag.enabled) {
                byCategory[category].enabled++;
            }
        });
        // By environment
        const byEnvironment = {};
        flags.forEach(flag => {
            const environments = flag.environments || ['default'];
            environments.forEach(env => {
                if (!byEnvironment[env]) {
                    byEnvironment[env] = { count: 0, enabled: 0 };
                }
                byEnvironment[env].count++;
                if (flag.enabled) {
                    byEnvironment[env].enabled++;
                }
            });
        });
        // Scheduled changes
        const now = new Date();
        const toEnable = flags
            .filter(f => !f.enabled && f.enabledFrom && new Date(f.enabledFrom) > now)
            .map(f => ({ flag: f.name, date: f.enabledFrom }));
        const toDisable = flags
            .filter(f => f.enabled && f.enabledUntil && new Date(f.enabledUntil) > now)
            .map(f => ({ flag: f.name, date: f.enabledUntil }));
        // Recent activity
        const recentActivity = this.db
            .getDatabase()
            .prepare(`
      SELECT flag_key, action, timestamp, user_id 
      FROM feature_flag_audit 
      ORDER BY timestamp DESC 
      LIMIT 10
    `)
            .all();
        return {
            totalFlags: flags.length,
            enabledFlags: enabledFlags.length,
            disabledFlags: flags.length - enabledFlags.length,
            byCategory,
            byEnvironment,
            scheduledChanges: { toEnable, toDisable },
            recentActivity: recentActivity.map(row => ({
                flag: row.flag_key,
                action: row.action,
                timestamp: row.timestamp,
                user: row.user_id,
            })),
        };
    }
    getEvaluationHistory(flagKey, limit = 100) {
        return this.db
            .getDatabase()
            .prepare(`
      SELECT * FROM feature_flag_evaluations 
      WHERE flag_key = ? 
      ORDER BY evaluated_at DESC 
      LIMIT ?
    `)
            .all(flagKey, limit);
    }
    getAuditLog(flagId, limit = 50) {
        const query = flagId
            ? 'SELECT * FROM feature_flag_audit WHERE flag_id = ? ORDER BY timestamp DESC LIMIT ?'
            : 'SELECT * FROM feature_flag_audit ORDER BY timestamp DESC LIMIT ?';
        const params = flagId ? [flagId, limit] : [limit];
        return this.db
            .getDatabase()
            .prepare(query)
            .all(...params);
    }
    // Bulk operations
    enableFlag(key, userId) {
        const flag = this.getFlagByKey(key);
        if (!flag) {
            throw new Error(`Feature flag not found: ${key}`);
        }
        this.updateFlag(flag.id, { enabled: true }, userId);
        this.logAudit(flag.id, key, 'enabled', { enabled: false }, { enabled: true }, userId);
    }
    disableFlag(key, userId) {
        const flag = this.getFlagByKey(key);
        if (!flag) {
            throw new Error(`Feature flag not found: ${key}`);
        }
        this.updateFlag(flag.id, { enabled: false }, userId);
        this.logAudit(flag.id, key, 'disabled', { enabled: true }, { enabled: false }, userId);
    }
    // Utility methods
    rowToFlag(row) {
        return {
            id: row.id,
            name: row.name,
            key: row.key,
            enabled: Boolean(row.enabled),
            description: row.description,
            environments: row.environments ? JSON.parse(row.environments) : undefined,
            users: row.users ? JSON.parse(row.users) : undefined,
            percentage: row.percentage,
            enabledFrom: row.enabled_from,
            enabledUntil: row.enabled_until,
            category: row.category,
            tags: row.tags ? JSON.parse(row.tags) : undefined,
            createdAt: row.created_at,
            updatedAt: row.updated_at,
            createdBy: row.created_by,
            lastModifiedBy: row.last_modified_by,
        };
    }
    logEvaluation(flagId, flagKey, enabled, reason, context) {
        // Only log if flag exists (flagId is not empty)
        if (flagId) {
            this.db
                .getDatabase()
                .prepare(`
        INSERT INTO feature_flag_evaluations (id, flag_id, flag_key, enabled, reason, context)
        VALUES (?, ?, ?, ?, ?, ?)
      `)
                .run((0, uuid_1.v4)(), flagId, flagKey, enabled ? 1 : 0, reason, JSON.stringify(context));
        }
    }
    logAudit(flagId, flagKey, action, oldValue, newValue, userId) {
        this.db
            .getDatabase()
            .prepare(`
      INSERT INTO feature_flag_audit (id, flag_id, flag_key, action, old_value, new_value, user_id)
      VALUES (?, ?, ?, ?, ?, ?, ?)
    `)
            .run((0, uuid_1.v4)(), flagId, flagKey, action, oldValue ? JSON.stringify(oldValue) : null, newValue ? JSON.stringify(newValue) : null, userId);
    }
    simpleHash(str) {
        let hash = 0;
        for (let i = 0; i < str.length; i++) {
            const char = str.charCodeAt(i);
            hash = (hash << 5) - hash + char;
            hash = hash & hash; // Convert to 32-bit integer
        }
        return Math.abs(hash);
    }
    // Predefined feature flags for common features
    static getDefaultFlags() {
        return [
            {
                name: 'Enhanced Search',
                key: 'enhanced_search',
                enabled: true,
                description: 'Enable enhanced search capabilities with filters',
                category: 'search',
                tags: ['core', 'stable'],
            },
            {
                name: 'Beta Features',
                key: 'beta_features',
                enabled: false,
                description: 'Enable experimental beta features',
                category: 'experimental',
                tags: ['beta', 'experimental'],
                environments: ['development', 'staging'],
            },
            {
                name: 'Advanced Analytics',
                key: 'advanced_analytics',
                enabled: false,
                description: 'Enable detailed analytics and metrics',
                category: 'analytics',
                tags: ['analytics', 'metrics'],
                percentage: 25, // 25% rollout
            },
            {
                name: 'Auto Compression',
                key: 'auto_compression',
                enabled: true,
                description: 'Automatically compress old context data',
                category: 'performance',
                tags: ['performance', 'storage'],
            },
        ];
    }
}
exports.FeatureFlagManager = FeatureFlagManager;
