/**
 * @ai-summary Repository for backup database operations
 * @ai-context Handles CRUD operations for backup_schedules, backup_history, backup_settings
 */

import { Pool } from 'pg';
import {
  BackupSchedule,
  BackupHistory,
  BackupSettingsMap,
  BackupFrequency,
  BackupType,
  BackupStatus,
  BackupMetadata,
  BackupCategory,
  ListBackupsParams,
  CRON_EXPRESSIONS,
} from '../domain/backup.types';

export class BackupRepository {
  constructor(private pool: Pool) {}

  // ============================================
  // Row Mappers (snake_case -> camelCase)
  // ============================================

  private mapScheduleRow(row: any): BackupSchedule {
    return {
      id: row.id,
      name: row.name,
      frequency: row.frequency as BackupFrequency,
      cronExpression: row.cron_expression,
      retentionCount: row.retention_count,
      isEnabled: row.is_enabled,
      lastRunAt: row.last_run_at ? new Date(row.last_run_at) : null,
      nextRunAt: row.next_run_at ? new Date(row.next_run_at) : null,
      createdAt: new Date(row.created_at),
      updatedAt: new Date(row.updated_at),
    };
  }

  private mapHistoryRow(row: any): BackupHistory {
    return {
      id: row.id,
      scheduleId: row.schedule_id,
      backupType: row.backup_type as BackupType,
      filename: row.filename,
      filePath: row.file_path,
      fileSizeBytes: parseInt(row.file_size_bytes, 10),
      databaseTablesCount: row.database_tables_count,
      documentsCount: row.documents_count,
      status: row.status as BackupStatus,
      errorMessage: row.error_message,
      startedAt: new Date(row.started_at),
      completedAt: row.completed_at ? new Date(row.completed_at) : null,
      createdBy: row.created_by,
      metadata: row.metadata as BackupMetadata,
      categories: (row.categories || ['hourly']) as BackupCategory[],
      expiresAt: row.expires_at ? new Date(row.expires_at) : null,
    };
  }

  // ============================================
  // Schedule Operations
  // ============================================

  async listSchedules(): Promise<BackupSchedule[]> {
    const result = await this.pool.query(
      `SELECT * FROM backup_schedules ORDER BY created_at ASC`
    );
    return result.rows.map(this.mapScheduleRow);
  }

  async getScheduleById(id: string): Promise<BackupSchedule | null> {
    const result = await this.pool.query(
      `SELECT * FROM backup_schedules WHERE id = $1`,
      [id]
    );
    return result.rows.length > 0 ? this.mapScheduleRow(result.rows[0]) : null;
  }

  async getEnabledSchedulesDue(): Promise<BackupSchedule[]> {
    const result = await this.pool.query(
      `SELECT * FROM backup_schedules
       WHERE is_enabled = true
       AND (next_run_at IS NULL OR next_run_at <= NOW())
       ORDER BY next_run_at ASC`
    );
    return result.rows.map(this.mapScheduleRow);
  }

  async createSchedule(
    name: string,
    frequency: BackupFrequency,
    retentionCount: number = 7,
    isEnabled: boolean = true
  ): Promise<BackupSchedule> {
    const cronExpression = CRON_EXPRESSIONS[frequency];
    const nextRunAt = this.calculateNextRun(frequency);

    const result = await this.pool.query(
      `INSERT INTO backup_schedules
       (name, frequency, cron_expression, retention_count, is_enabled, next_run_at)
       VALUES ($1, $2, $3, $4, $5, $6)
       RETURNING *`,
      [name, frequency, cronExpression, retentionCount, isEnabled, nextRunAt]
    );
    return this.mapScheduleRow(result.rows[0]);
  }

  async updateSchedule(
    id: string,
    updates: Partial<{
      name: string;
      frequency: BackupFrequency;
      retentionCount: number;
      isEnabled: boolean;
    }>
  ): Promise<BackupSchedule | null> {
    const setClauses: string[] = [];
    const values: any[] = [];
    let paramIndex = 1;

    if (updates.name !== undefined) {
      setClauses.push(`name = $${paramIndex++}`);
      values.push(updates.name);
    }
    if (updates.frequency !== undefined) {
      setClauses.push(`frequency = $${paramIndex++}`);
      values.push(updates.frequency);
      setClauses.push(`cron_expression = $${paramIndex++}`);
      values.push(CRON_EXPRESSIONS[updates.frequency]);
      setClauses.push(`next_run_at = $${paramIndex++}`);
      values.push(this.calculateNextRun(updates.frequency));
    }
    if (updates.retentionCount !== undefined) {
      setClauses.push(`retention_count = $${paramIndex++}`);
      values.push(updates.retentionCount);
    }
    if (updates.isEnabled !== undefined) {
      setClauses.push(`is_enabled = $${paramIndex++}`);
      values.push(updates.isEnabled);
    }

    if (setClauses.length === 0) {
      return this.getScheduleById(id);
    }

    values.push(id);
    const result = await this.pool.query(
      `UPDATE backup_schedules SET ${setClauses.join(', ')} WHERE id = $${paramIndex} RETURNING *`,
      values
    );
    return result.rows.length > 0 ? this.mapScheduleRow(result.rows[0]) : null;
  }

  async deleteSchedule(id: string): Promise<boolean> {
    const result = await this.pool.query(
      `DELETE FROM backup_schedules WHERE id = $1`,
      [id]
    );
    return (result.rowCount ?? 0) > 0;
  }

  async updateScheduleLastRun(id: string, nextRunAt: Date): Promise<void> {
    await this.pool.query(
      `UPDATE backup_schedules SET last_run_at = NOW(), next_run_at = $1 WHERE id = $2`,
      [nextRunAt, id]
    );
  }

  // ============================================
  // History Operations
  // ============================================

  async listBackups(params: ListBackupsParams): Promise<{
    items: BackupHistory[];
    total: number;
  }> {
    const page = params.page || 1;
    const pageSize = params.pageSize || 20;
    const offset = (page - 1) * pageSize;
    const sortBy = params.sortBy || 'startedAt';
    const sortOrder = params.sortOrder || 'desc';

    const sortColumnMap: Record<string, string> = {
      startedAt: 'started_at',
      fileSizeBytes: 'file_size_bytes',
      status: 'status',
    };
    const sortColumn = sortColumnMap[sortBy] || 'started_at';

    const whereClauses: string[] = [];
    const values: any[] = [];
    let paramIndex = 1;

    if (params.status) {
      whereClauses.push(`status = $${paramIndex++}`);
      values.push(params.status);
    }
    if (params.backupType) {
      whereClauses.push(`backup_type = $${paramIndex++}`);
      values.push(params.backupType);
    }

    const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(' AND ')}` : '';

    // Get total count
    const countResult = await this.pool.query(
      `SELECT COUNT(*) as total FROM backup_history ${whereClause}`,
      values
    );
    const total = parseInt(countResult.rows[0].total, 10);

    // Get paginated items
    values.push(pageSize, offset);
    const result = await this.pool.query(
      `SELECT * FROM backup_history ${whereClause}
       ORDER BY ${sortColumn} ${sortOrder.toUpperCase()}
       LIMIT $${paramIndex++} OFFSET $${paramIndex}`,
      values
    );

    return {
      items: result.rows.map(this.mapHistoryRow),
      total,
    };
  }

  async getBackupById(id: string): Promise<BackupHistory | null> {
    const result = await this.pool.query(
      `SELECT * FROM backup_history WHERE id = $1`,
      [id]
    );
    return result.rows.length > 0 ? this.mapHistoryRow(result.rows[0]) : null;
  }

  async getBackupsBySchedule(scheduleId: string): Promise<BackupHistory[]> {
    const result = await this.pool.query(
      `SELECT * FROM backup_history
       WHERE schedule_id = $1 AND status = 'completed'
       ORDER BY started_at DESC`,
      [scheduleId]
    );
    return result.rows.map(this.mapHistoryRow);
  }

  async getLastBackupForSchedule(scheduleId: string): Promise<BackupHistory | null> {
    const result = await this.pool.query(
      `SELECT * FROM backup_history
       WHERE schedule_id = $1
       ORDER BY started_at DESC
       LIMIT 1`,
      [scheduleId]
    );
    return result.rows.length > 0 ? this.mapHistoryRow(result.rows[0]) : null;
  }

  async createBackupRecord(data: {
    scheduleId?: string | null;
    backupType: BackupType;
    filename: string;
    filePath: string;
    fileSizeBytes: number;
    createdBy?: string | null;
    metadata?: BackupMetadata;
    categories?: BackupCategory[];
    expiresAt?: Date | null;
  }): Promise<BackupHistory> {
    const result = await this.pool.query(
      `INSERT INTO backup_history
       (schedule_id, backup_type, filename, file_path, file_size_bytes, status, created_by, metadata, categories, expires_at)
       VALUES ($1, $2, $3, $4, $5, 'in_progress', $6, $7, $8, $9)
       RETURNING *`,
      [
        data.scheduleId || null,
        data.backupType,
        data.filename,
        data.filePath,
        data.fileSizeBytes,
        data.createdBy || null,
        JSON.stringify(data.metadata || {}),
        data.categories || ['hourly'],
        data.expiresAt || null,
      ]
    );
    return this.mapHistoryRow(result.rows[0]);
  }

  async completeBackup(
    id: string,
    data: {
      fileSizeBytes: number;
      databaseTablesCount: number;
      documentsCount: number;
      metadata: BackupMetadata;
      filename?: string;
      filePath?: string;
    }
  ): Promise<BackupHistory | null> {
    const result = await this.pool.query(
      `UPDATE backup_history SET
       status = 'completed',
       file_size_bytes = $1,
       database_tables_count = $2,
       documents_count = $3,
       metadata = $4,
       filename = COALESCE($5, filename),
       file_path = COALESCE($6, file_path),
       completed_at = NOW()
       WHERE id = $7
       RETURNING *`,
      [
        data.fileSizeBytes,
        data.databaseTablesCount,
        data.documentsCount,
        JSON.stringify(data.metadata),
        data.filename || null,
        data.filePath || null,
        id,
      ]
    );
    return result.rows.length > 0 ? this.mapHistoryRow(result.rows[0]) : null;
  }

  async failBackup(id: string, errorMessage: string): Promise<BackupHistory | null> {
    const result = await this.pool.query(
      `UPDATE backup_history SET
       status = 'failed',
       error_message = $1,
       completed_at = NOW()
       WHERE id = $2
       RETURNING *`,
      [errorMessage, id]
    );
    return result.rows.length > 0 ? this.mapHistoryRow(result.rows[0]) : null;
  }

  async deleteBackupRecord(id: string): Promise<boolean> {
    const result = await this.pool.query(
      `DELETE FROM backup_history WHERE id = $1`,
      [id]
    );
    return (result.rowCount ?? 0) > 0;
  }

  async getOldestBackupsForSchedule(
    scheduleId: string,
    keepCount: number
  ): Promise<BackupHistory[]> {
    const result = await this.pool.query(
      `SELECT * FROM backup_history
       WHERE schedule_id = $1 AND status = 'completed'
       ORDER BY started_at DESC
       OFFSET $2`,
      [scheduleId, keepCount]
    );
    return result.rows.map(this.mapHistoryRow);
  }

  // ============================================
  // Tiered Retention Operations
  // ============================================

  /**
   * Gets all completed backups that have a specific category.
   * Sorted by started_at DESC (newest first).
   */
  async getBackupsByCategory(category: BackupCategory): Promise<BackupHistory[]> {
    const result = await this.pool.query(
      `SELECT * FROM backup_history
       WHERE status = 'completed'
       AND $1 = ANY(categories)
       ORDER BY started_at DESC`,
      [category]
    );
    return result.rows.map(row => this.mapHistoryRow(row));
  }

  /**
   * Gets all completed backups for tiered retention processing.
   * Returns backups sorted by started_at DESC.
   */
  async getAllCompletedBackups(): Promise<BackupHistory[]> {
    const result = await this.pool.query(
      `SELECT * FROM backup_history
       WHERE status = 'completed'
       ORDER BY started_at DESC`
    );
    return result.rows.map(row => this.mapHistoryRow(row));
  }

  // ============================================
  // Settings Operations
  // ============================================

  async getSettings(): Promise<BackupSettingsMap> {
    const result = await this.pool.query(
      `SELECT setting_key, setting_value FROM backup_settings`
    );

    const settings: BackupSettingsMap = {
      emailOnSuccess: true,
      emailOnFailure: true,
      adminEmail: '',
      maxBackupSizeMb: 1024,
      compressionEnabled: true,
    };

    for (const row of result.rows) {
      switch (row.setting_key) {
        case 'email_on_success':
          settings.emailOnSuccess = row.setting_value === 'true';
          break;
        case 'email_on_failure':
          settings.emailOnFailure = row.setting_value === 'true';
          break;
        case 'admin_email':
          settings.adminEmail = row.setting_value;
          break;
        case 'max_backup_size_mb':
          settings.maxBackupSizeMb = parseInt(row.setting_value, 10);
          break;
        case 'compression_enabled':
          settings.compressionEnabled = row.setting_value === 'true';
          break;
      }
    }

    return settings;
  }

  async updateSettings(updates: Partial<BackupSettingsMap>): Promise<void> {
    const keyMap: Record<keyof BackupSettingsMap, string> = {
      emailOnSuccess: 'email_on_success',
      emailOnFailure: 'email_on_failure',
      adminEmail: 'admin_email',
      maxBackupSizeMb: 'max_backup_size_mb',
      compressionEnabled: 'compression_enabled',
    };

    for (const [key, dbKey] of Object.entries(keyMap)) {
      const value = updates[key as keyof BackupSettingsMap];
      if (value !== undefined) {
        await this.pool.query(
          `UPDATE backup_settings SET setting_value = $1 WHERE setting_key = $2`,
          [String(value), dbKey]
        );
      }
    }
  }

  // ============================================
  // Helper Methods
  // ============================================

  private calculateNextRun(frequency: BackupFrequency): Date {
    const now = new Date();
    const next = new Date(now);

    switch (frequency) {
      case 'hourly':
        next.setHours(next.getHours() + 1);
        next.setMinutes(0, 0, 0);
        break;
      case 'daily':
        next.setDate(next.getDate() + 1);
        next.setHours(3, 0, 0, 0);
        break;
      case 'weekly': {
        const daysUntilSunday = (7 - next.getDay()) % 7 || 7;
        next.setDate(next.getDate() + daysUntilSunday);
        next.setHours(3, 0, 0, 0);
        break;
      }
      case 'monthly':
        next.setMonth(next.getMonth() + 1);
        next.setDate(1);
        next.setHours(3, 0, 0, 0);
        break;
    }

    return next;
  }
}
