import { createClient } from '@supabase/supabase-js';
import { logError, logUserActivity } from './logger';
import { captureException } from './sentry';

// Environment variables
const SUPABASE_URL = process.env.SUPABASE_URL!;
const SUPABASE_SERVICE_KEY = process.env.SUPABASE_SERVICE_KEY!;

// Create admin client for backup operations
const supabaseAdmin = createClient(SUPABASE_URL, SUPABASE_SERVICE_KEY);

/**
 * Backup configuration
 */
interface BackupConfig {
  retention: {
    daily: number;    // Days to keep daily backups
    weekly: number;   // Weeks to keep weekly backups
    monthly: number;  // Months to keep monthly backups
  };
  tables: string[];   // Tables to backup
  storage: {
    bucket: string;   // Storage bucket name
    path: string;     // Path prefix for backups
  };
}

const DEFAULT_BACKUP_CONFIG: BackupConfig = {
  retention: {
    daily: 7,
    weekly: 4,
    monthly: 12
  },
  tables: [
    'users',
    'organizations', 
    'events',
    'tickets',
    'payouts',
    'audit_logs'
  ],
  storage: {
    bucket: 'backups',
    path: 'database'
  }
};

/**
 * Backup metadata
 */
interface BackupMetadata {
  id: string;
  timestamp: string;
  type: 'daily' | 'weekly' | 'monthly';
  size: number;
  tables: string[];
  checksum: string;
  status: 'in_progress' | 'completed' | 'failed';
  error?: string;
}

/**
 * Database backup manager
 */
export class BackupManager {
  private config: BackupConfig;

  constructor(config: BackupConfig = DEFAULT_BACKUP_CONFIG) {
    this.config = config;
  }

  /**
   * Create a full database backup
   */
  async createBackup(type: 'daily' | 'weekly' | 'monthly' = 'daily'): Promise<BackupMetadata> {
    const backupId = `${type}-${Date.now()}`;
    const timestamp = new Date().toISOString();
    
    const metadata: BackupMetadata = {
      id: backupId,
      timestamp,
      type,
      size: 0,
      tables: this.config.tables,
      checksum: '',
      status: 'in_progress'
    };

    try {
      logUserActivity({
        action: 'backup_started',
        userId: 'system',
        resourceType: 'database',
        resourceId: backupId
      });

      // Create backup data
      const backupData: Record<string, any[]> = {};
      let totalSize = 0;

      for (const table of this.config.tables) {
        try {
          const { data, error } = await supabaseAdmin
            .from(table)
            .select('*');

          if (error) {
            throw new Error(`Failed to backup table ${table}: ${error.message}`);
          }

          backupData[table] = data || [];
          totalSize += JSON.stringify(data).length;
        } catch (error) {
          console.error(`Error backing up table ${table}:`, error);
          throw error;
        }
      }

      // Create backup file
      const backupContent = JSON.stringify({
        metadata: {
          id: backupId,
          timestamp,
          type,
          tables: this.config.tables,
          version: '1.0'
        },
        data: backupData
      }, null, 2);

      // Calculate checksum
      const checksum = await this.calculateChecksum(backupContent);
      metadata.checksum = checksum;
      metadata.size = backupContent.length;

      // Upload to storage
      const fileName = `${this.config.storage.path}/${backupId}.json`;
      
      const { error: uploadError } = await supabaseAdmin.storage
        .from(this.config.storage.bucket)
        .upload(fileName, backupContent, {
          contentType: 'application/json',
          cacheControl: '3600'
        });

      if (uploadError) {
        throw new Error(`Failed to upload backup: ${uploadError.message}`);
      }

      // Save metadata
      await this.saveBackupMetadata(metadata);

      metadata.status = 'completed';

      logUserActivity({
        action: 'backup_completed',
        userId: 'system',
        resourceType: 'database',
        resourceId: backupId,
        details: {
          size: metadata.size,
          tables: metadata.tables.length,
          checksum: metadata.checksum
        }
      });

      return metadata;

    } catch (error) {
      metadata.status = 'failed';
      metadata.error = error.message;

      logError(error, {
        requestId: backupId,
        additionalContext: {
          operation: 'database_backup',
          type,
          tables: this.config.tables
        }
      });

      captureException(error, {
        additionalData: {
          backupId,
          type,
          tables: this.config.tables
        }
      });

      throw error;
    }
  }

  /**
   * Restore database from backup
   */
  async restoreBackup(backupId: string, options: {
    tables?: string[];
    dryRun?: boolean;
    confirmRestore?: boolean;
  } = {}): Promise<void> {
    if (!options.confirmRestore) {
      throw new Error('Restore confirmation required. Set confirmRestore: true');
    }

    try {
      logUserActivity({
        action: 'restore_started',
        userId: 'system',
        resourceType: 'database',
        resourceId: backupId
      });

      // Download backup file
      const fileName = `${this.config.storage.path}/${backupId}.json`;
      
      const { data: backupFile, error: downloadError } = await supabaseAdmin.storage
        .from(this.config.storage.bucket)
        .download(fileName);

      if (downloadError) {
        throw new Error(`Failed to download backup: ${downloadError.message}`);
      }

      // Parse backup data
      const backupContent = await backupFile.text();
      const backup = JSON.parse(backupContent);

      // Verify checksum
      const expectedChecksum = await this.calculateChecksum(backupContent);
      if (backup.metadata.checksum !== expectedChecksum) {
        throw new Error('Backup file integrity check failed');
      }

      const tablesToRestore = options.tables || backup.metadata.tables;

      if (options.dryRun) {
        console.log('DRY RUN: Would restore tables:', tablesToRestore);
        console.log('Backup metadata:', backup.metadata);
        return;
      }

      // Restore each table
      for (const table of tablesToRestore) {
        if (!backup.data[table]) {
          console.warn(`Table ${table} not found in backup`);
          continue;
        }

        try {
          // Clear existing data (be very careful here!)
          const { error: deleteError } = await supabaseAdmin
            .from(table)
            .delete()
            .neq('id', '00000000-0000-0000-0000-000000000000'); // Delete all rows

          if (deleteError) {
            throw new Error(`Failed to clear table ${table}: ${deleteError.message}`);
          }

          // Insert backup data
          const { error: insertError } = await supabaseAdmin
            .from(table)
            .insert(backup.data[table]);

          if (insertError) {
            throw new Error(`Failed to restore table ${table}: ${insertError.message}`);
          }

          console.log(`Restored ${backup.data[table].length} rows to table ${table}`);
        } catch (error) {
          console.error(`Error restoring table ${table}:`, error);
          throw error;
        }
      }

      logUserActivity({
        action: 'restore_completed',
        userId: 'system',
        resourceType: 'database',
        resourceId: backupId,
        details: {
          tables: tablesToRestore
        }
      });

    } catch (error) {
      logError(error, {
        requestId: backupId,
        additionalContext: {
          operation: 'database_restore',
          tables: options.tables
        }
      });

      captureException(error, {
        additionalData: {
          backupId,
          tables: options.tables
        }
      });

      throw error;
    }
  }

  /**
   * List available backups
   */
  async listBackups(): Promise<BackupMetadata[]> {
    try {
      const { data: files, error } = await supabaseAdmin.storage
        .from(this.config.storage.bucket)
        .list(this.config.storage.path);

      if (error) {
        throw new Error(`Failed to list backups: ${error.message}`);
      }

      const backups: BackupMetadata[] = [];

      for (const file of files) {
        if (file.name.endsWith('.json')) {
          try {
            const metadata = await this.getBackupMetadata(file.name.replace('.json', ''));
            if (metadata) {
              backups.push(metadata);
            }
          } catch (error) {
            console.warn(`Failed to get metadata for backup ${file.name}:`, error);
          }
        }
      }

      return backups.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
    } catch (error) {
      logError(error, {
        additionalContext: {
          operation: 'list_backups'
        }
      });
      throw error;
    }
  }

  /**
   * Clean up old backups based on retention policy
   */
  async cleanupBackups(): Promise<void> {
    try {
      const backups = await this.listBackups();
      const now = new Date();
      const backupsToDelete: string[] = [];

      for (const backup of backups) {
        const backupDate = new Date(backup.timestamp);
        const ageInDays = (now.getTime() - backupDate.getTime()) / (1000 * 60 * 60 * 24);

        let shouldDelete = false;

        switch (backup.type) {
          case 'daily':
            shouldDelete = ageInDays > this.config.retention.daily;
            break;
          case 'weekly':
            shouldDelete = ageInDays > (this.config.retention.weekly * 7);
            break;
          case 'monthly':
            shouldDelete = ageInDays > (this.config.retention.monthly * 30);
            break;
        }

        if (shouldDelete) {
          backupsToDelete.push(backup.id);
        }
      }

      // Delete old backups
      for (const backupId of backupsToDelete) {
        try {
          const fileName = `${this.config.storage.path}/${backupId}.json`;
          
          const { error } = await supabaseAdmin.storage
            .from(this.config.storage.bucket)
            .remove([fileName]);

          if (error) {
            console.error(`Failed to delete backup ${backupId}:`, error);
          } else {
            console.log(`Deleted old backup: ${backupId}`);
          }
        } catch (error) {
          console.error(`Error deleting backup ${backupId}:`, error);
        }
      }

      logUserActivity({
        action: 'backup_cleanup',
        userId: 'system',
        resourceType: 'database',
        details: {
          deletedCount: backupsToDelete.length,
          backupIds: backupsToDelete
        }
      });

    } catch (error) {
      logError(error, {
        additionalContext: {
          operation: 'cleanup_backups'
        }
      });
      throw error;
    }
  }

  /**
   * Calculate file checksum
   */
  private async calculateChecksum(content: string): Promise<string> {
    const encoder = new TextEncoder();
    const data = encoder.encode(content);
    
    if (typeof crypto !== 'undefined' && crypto.subtle) {
      const hashBuffer = await crypto.subtle.digest('SHA-256', data);
      const hashArray = Array.from(new Uint8Array(hashBuffer));
      return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    } else {
      // Fallback for Node.js environment
      const crypto = require('crypto');
      return crypto.createHash('sha256').update(content).digest('hex');
    }
  }

  /**
   * Save backup metadata
   */
  private async saveBackupMetadata(metadata: BackupMetadata): Promise<void> {
    // This would typically save to a metadata table
    // For now, we'll store it as a separate file
    const metadataFileName = `${this.config.storage.path}/metadata/${metadata.id}.json`;
    
    const { error } = await supabaseAdmin.storage
      .from(this.config.storage.bucket)
      .upload(metadataFileName, JSON.stringify(metadata, null, 2), {
        contentType: 'application/json',
        cacheControl: '3600'
      });

    if (error) {
      console.warn(`Failed to save backup metadata: ${error.message}`);
    }
  }

  /**
   * Get backup metadata
   */
  private async getBackupMetadata(backupId: string): Promise<BackupMetadata | null> {
    try {
      const metadataFileName = `${this.config.storage.path}/metadata/${backupId}.json`;
      
      const { data, error } = await supabaseAdmin.storage
        .from(this.config.storage.bucket)
        .download(metadataFileName);

      if (error) {
        return null;
      }

      const content = await data.text();
      return JSON.parse(content);
    } catch (error) {
      return null;
    }
  }
}

/**
 * Scheduled backup runner
 */
export class BackupScheduler {
  private backupManager: BackupManager;
  private intervals: Map<string, NodeJS.Timeout> = new Map();

  constructor(backupManager: BackupManager) {
    this.backupManager = backupManager;
  }

  /**
   * Start automated backups
   */
  startScheduledBackups() {
    // Daily backups at 2 AM
    this.scheduleBackup('daily', '0 2 * * *', 'daily');
    
    // Weekly backups on Sunday at 3 AM
    this.scheduleBackup('weekly', '0 3 * * 0', 'weekly');
    
    // Monthly backups on the 1st at 4 AM
    this.scheduleBackup('monthly', '0 4 1 * *', 'monthly');

    console.log('Backup scheduler started');
  }

  /**
   * Stop all scheduled backups
   */
  stopScheduledBackups() {
    for (const [name, interval] of this.intervals) {
      clearInterval(interval);
      console.log(`Stopped ${name} backup schedule`);
    }
    this.intervals.clear();
  }

  /**
   * Schedule a backup with cron-like syntax (simplified)
   */
  private scheduleBackup(name: string, cronExpression: string, type: 'daily' | 'weekly' | 'monthly') {
    // For production, use a proper cron library like node-cron
    // This is a simplified version for demonstration
    
    const runBackup = async () => {
      try {
        console.log(`Starting ${name} backup...`);
        await this.backupManager.createBackup(type);
        console.log(`${name} backup completed successfully`);
        
        // Cleanup old backups after successful backup
        await this.backupManager.cleanupBackups();
      } catch (error) {
        console.error(`${name} backup failed:`, error);
      }
    };

    // For demonstration, we'll run backups based on simple intervals
    // In production, replace with proper cron scheduling
    let intervalMs: number;
    
    switch (type) {
      case 'daily':
        intervalMs = 24 * 60 * 60 * 1000; // 24 hours
        break;
      case 'weekly':
        intervalMs = 7 * 24 * 60 * 60 * 1000; // 7 days
        break;
      case 'monthly':
        intervalMs = 30 * 24 * 60 * 60 * 1000; // 30 days
        break;
    }

    const interval = setInterval(runBackup, intervalMs);
    this.intervals.set(name, interval);
  }
}

// Export singleton instances
export const backupManager = new BackupManager();
export const backupScheduler = new BackupScheduler(backupManager);

// Disaster recovery utilities
export const DisasterRecovery = {
  /**
   * Create a point-in-time recovery backup
   */
  async createPointInTimeBackup(label: string): Promise<BackupMetadata> {
    const customConfig = {
      ...DEFAULT_BACKUP_CONFIG,
      storage: {
        bucket: 'backups',
        path: `disaster-recovery/${label}`
      }
    };

    const manager = new BackupManager(customConfig);
    return await manager.createBackup('daily');
  },

  /**
   * Verify system integrity after recovery
   */
  async verifySystemIntegrity(): Promise<{
    status: 'healthy' | 'degraded' | 'critical';
    checks: Array<{
      name: string;
      status: 'pass' | 'fail';
      message: string;
    }>;
  }> {
    const checks = [];

    // Check database connectivity
    try {
      const { data, error } = await supabaseAdmin
        .from('users')
        .select('count')
        .limit(1);
      
      checks.push({
        name: 'Database Connectivity',
        status: error ? 'fail' : 'pass',
        message: error ? error.message : 'Database is accessible'
      });
    } catch (error) {
      checks.push({
        name: 'Database Connectivity',
        status: 'fail',
        message: error.message
      });
    }

    // Check critical tables exist
    const criticalTables = ['users', 'organizations', 'events', 'tickets'];
    for (const table of criticalTables) {
      try {
        const { data, error } = await supabaseAdmin
          .from(table)
          .select('count')
          .limit(1);
        
        checks.push({
          name: `Table ${table}`,
          status: error ? 'fail' : 'pass',
          message: error ? error.message : `Table ${table} is accessible`
        });
      } catch (error) {
        checks.push({
          name: `Table ${table}`,
          status: 'fail',
          message: error.message
        });
      }
    }

    // Determine overall status
    const failedChecks = checks.filter(check => check.status === 'fail').length;
    const status = failedChecks === 0 ? 'healthy' : 
                  failedChecks <= 2 ? 'degraded' : 'critical';

    return { status, checks };
  }
};