/**
 * Backup Engine
 * Core logic for creating snapshots and restoring entities.
 * All operations are logged via the audit logger.
 */

import { prisma } from '@/lib/db';
import { auditLogger } from '@/lib/autonomy/audit-logger';
import { VOCAB } from '@/lib/white-label/constants';

// Entity types that can be backed up, grouped by category
export const BACKUP_ENTITY_GROUPS: Record<string, string[]> = {
  'Agent Profiles': ['DomainAgent'],
  'Agent Memories': [
    'DomainAgentMemory', 'GhostMemory', 'Memory',
    'TesMemory', 'TesaMemory', 'AICompanionMemory', 'MemorySystemConfig',
  ],
  'Reports': ['ReportDefinition', 'ReportWidget', 'ReportSchedule', 'ReportSubscription'],
  'Integrations': ['Integration', 'ModuleConfig'],
  [VOCAB.factory]: ['DarkFactoryJob', 'DarkFactoryProject', 'GenerationTask', 'GeneratedArtifact', 'Deployment'],
  'Documents': ['Document'],
  'Workflows': ['Workflow', 'WorkflowStep'],
  [`${VOCAB.observer} & Security`]: [`${VOCAB.observer}Rule`, `${VOCAB.observer}Alert`, 'GhostRule'],
};

export const ALL_ENTITY_TYPES = Object.values(BACKUP_ENTITY_GROUPS).flat();

// Map entity type names to prisma model accessors
const MODEL_MAP: Record<string, string> = {
  DomainAgent: 'domainAgent',
  DomainAgentMemory: 'domainAgentMemory',
  GhostMemory: 'ghostMemory',
  Memory: 'memory',
  TesMemory: 'tesMemory',
  TesaMemory: 'tesaMemory',
  AICompanionMemory: 'aICompanionMemory',
  MemorySystemConfig: 'memorySystemConfig',
  ReportDefinition: 'reportDefinition',
  ReportWidget: 'reportWidget',
  ReportSchedule: 'reportSchedule',
  ReportSubscription: 'reportSubscription',
  Integration: 'integration',
  ModuleConfig: 'moduleConfig',
  DarkFactoryJob: 'darkFactoryJob',
  DarkFactoryProject: 'darkFactoryProject',
  GenerationTask: 'generationTask',
  GeneratedArtifact: 'generatedArtifact',
  Deployment: 'deployment',
  Document: 'document',
  Workflow: 'workflow',
  WorkflowStep: 'workflowStep',
  SentinelRule: 'sentinelRule',
  SentinelAlert: 'sentinelAlert',
  GhostRule: 'ghostRule',
};

interface CreateBackupOptions {
  name: string;
  type?: 'SNAPSHOT' | 'FULL_EXPORT' | 'SELECTIVE';
  trigger?: 'MANUAL' | 'AUTO' | 'PRE_OPERATION';
  entityTypes?: string[]; // if empty/undefined, backs up ALL
  createdById?: string;
  createdByAgent?: string;
  notes?: string;
  retentionDays?: number;
}

/**
 * Create a full or selective backup snapshot
 */
export async function createBackup(options: CreateBackupOptions) {
  const typesToBackup = options.entityTypes?.length
    ? options.entityTypes.filter(t => ALL_ENTITY_TYPES.includes(t))
    : ALL_ENTITY_TYPES;

  // Set actor context for audit logging
  auditLogger.setContext({
    userId: options.createdById,
    agentId: options.createdByAgent,
  });

  // Create the backup record
  const backup = await prisma.backup.create({
    data: {
      name: options.name,
      type: options.type || 'SNAPSHOT',
      status: 'IN_PROGRESS',
      trigger: options.trigger || 'MANUAL',
      entityTypes: typesToBackup,
      createdById: options.createdById,
      createdByAgent: options.createdByAgent,
      notes: options.notes,
      expiresAt: options.retentionDays
        ? new Date(Date.now() + options.retentionDays * 24 * 60 * 60 * 1000)
        : null,
    },
  });

  await auditLogger.log({
    action: 'system_event',
    resource: 'system',
    resourceId: backup.id,
    description: `Backup started: ${options.name} (${options.type || 'SNAPSHOT'}, trigger: ${options.trigger || 'MANUAL'})`,
    metadata: { entityTypes: typesToBackup, backupId: backup.id },
  });

  try {
    const entityCounts: Record<string, number> = {};
    let totalItems = 0;
    let sizeEstimate = 0;

    for (const entityType of typesToBackup) {
      const modelKey = MODEL_MAP[entityType];
      if (!modelKey) continue;

      const model = (prisma as any)[modelKey];
      if (!model) continue;

      try {
        const records = await model.findMany();
        entityCounts[entityType] = records.length;

        if (records.length > 0) {
          // Batch create backup items
          const itemsData = records.map((record: any) => ({
            backupId: backup.id,
            entityType,
            entityId: record.id,
            data: record,
            version: 1,
          }));

          // Use createMany for performance
          await prisma.backupItem.createMany({ data: itemsData });
          totalItems += records.length;

          // Rough size estimate
          const jsonSize = JSON.stringify(records).length;
          sizeEstimate += Math.ceil(jsonSize / 1024);
        }
      } catch (err: any) {
        console.error(`Backup error for ${entityType}:`, err.message);
        entityCounts[entityType] = -1; // Mark as failed
      }
    }

    // Update backup with final stats
    await prisma.backup.update({
      where: { id: backup.id },
      data: {
        status: 'COMPLETED',
        entityCounts,
        totalItems,
        sizeEstimateKb: sizeEstimate,
      },
    });

    await auditLogger.log({
      action: 'system_event',
      resource: 'system',
      resourceId: backup.id,
      description: `Backup completed: ${options.name} — ${totalItems} items, ~${sizeEstimate}KB`,
      metadata: { entityCounts, totalItems, sizeEstimate },
    });

    return { success: true, backup: { ...backup, entityCounts, totalItems, sizeEstimateKb: sizeEstimate, status: 'COMPLETED' } };
  } catch (err: any) {
    await prisma.backup.update({
      where: { id: backup.id },
      data: { status: 'FAILED', errorMessage: err.message },
    });

    await auditLogger.logError('system', backup.id, err, { backupName: options.name });

    return { success: false, error: err.message, backupId: backup.id };
  } finally {
    auditLogger.clearContext();
  }
}

/**
 * Restore entities from a backup. Supports full or selective restore.
 * Uses upsert to avoid duplicates and preserve existing data.
 */
export async function restoreFromBackup(options: {
  backupId: string;
  entityTypes?: string[]; // if empty, restore all
  entityIds?: string[]; // if provided, only restore specific entities
  performedById?: string;
  performedByAgent?: string;
}) {
  auditLogger.setContext({
    userId: options.performedById,
    agentId: options.performedByAgent,
  });

  const backup = await prisma.backup.findUnique({
    where: { id: options.backupId },
  });

  if (!backup) throw new Error('Backup not found');
  if (backup.status !== 'COMPLETED') throw new Error('Cannot restore from an incomplete backup');

  // Build query filter
  const where: any = { backupId: options.backupId };
  if (options.entityTypes?.length) {
    where.entityType = { in: options.entityTypes };
  }
  if (options.entityIds?.length) {
    where.entityId = { in: options.entityIds };
  }

  const items = await prisma.backupItem.findMany({ where });

  await auditLogger.log({
    action: 'import',
    resource: 'system',
    resourceId: options.backupId,
    description: `Restore started from backup "${backup.name}" — ${items.length} items`,
    metadata: {
      backupId: options.backupId,
      entityTypes: options.entityTypes,
      entityIds: options.entityIds,
      itemCount: items.length,
    },
  });

  const results: { entityType: string; entityId: string; status: 'restored' | 'failed'; error?: string }[] = [];

  for (const item of items) {
    const modelKey = MODEL_MAP[item.entityType];
    if (!modelKey) {
      results.push({ entityType: item.entityType, entityId: item.entityId, status: 'failed', error: 'Unknown entity type' });
      continue;
    }

    const model = (prisma as any)[modelKey];
    if (!model) {
      results.push({ entityType: item.entityType, entityId: item.entityId, status: 'failed', error: 'Model not found' });
      continue;
    }

    try {
      const data = item.data as Record<string, any>;
      const { id, createdAt, updatedAt, ...restData } = data;

      // Clean up relation fields that can't be directly set
      const cleanData = cleanRelationFields(item.entityType, restData);

      await model.upsert({
        where: { id: item.entityId },
        create: { id: item.entityId, ...cleanData },
        update: cleanData,
      });

      results.push({ entityType: item.entityType, entityId: item.entityId, status: 'restored' });
    } catch (err: any) {
      results.push({ entityType: item.entityType, entityId: item.entityId, status: 'failed', error: err.message });
    }
  }

  const restored = results.filter(r => r.status === 'restored').length;
  const failed = results.filter(r => r.status === 'failed').length;

  await auditLogger.log({
    action: 'import',
    resource: 'system',
    resourceId: options.backupId,
    description: `Restore completed: ${restored} restored, ${failed} failed`,
    metadata: { restored, failed, results: results.filter(r => r.status === 'failed') },
    success: failed === 0,
  });

  auditLogger.clearContext();

  return { restored, failed, total: items.length, results };
}

/**
 * Remove relation/computed fields that would cause upsert errors.
 * Each entity type may have different relation fields to strip.
 */
function cleanRelationFields(entityType: string, data: Record<string, any>): Record<string, any> {
  const clean = { ...data };

  // Common relation fields that are objects/arrays (not scalar FK fields)
  const alwaysRemove = [
    'items', 'children', 'parent', 'backup', 'user', 'role', 'roles',
    'permissions', 'messages', 'conversations', 'memories', 'agents',
    'widgets', 'schedules', 'subscriptions', 'generatedReports',
    'deployments', 'validations', 'artifacts', 'tasks', 'steps',
    'executions', 'alerts', 'participants',
  ];

  for (const key of alwaysRemove) {
    if (clean[key] && typeof clean[key] === 'object' && !Array.isArray(clean[key])) {
      // Only remove if it looks like a relation object (has an 'id' property)
      if ('id' in clean[key]) delete clean[key];
    }
    if (Array.isArray(clean[key])) {
      delete clean[key];
    }
  }

  return clean;
}

/**
 * Delete expired backups (for scheduled cleanup)
 */
export async function cleanupExpiredBackups() {
  const expired = await prisma.backup.findMany({
    where: {
      expiresAt: { lte: new Date() },
    },
    select: { id: true, name: true },
  });

  for (const backup of expired) {
    await prisma.backup.delete({ where: { id: backup.id } });
    await auditLogger.log({
      action: 'delete',
      resource: 'system',
      resourceId: backup.id,
      description: `Expired backup cleaned up: ${backup.name}`,
    });
  }

  return expired.length;
}

/**
 * Get backup summary statistics
 */
export async function getBackupStats() {
  const [total, completed, failed, latestBackup, totalItems, schedules] = await Promise.all([
    prisma.backup.count(),
    prisma.backup.count({ where: { status: 'COMPLETED' } }),
    prisma.backup.count({ where: { status: 'FAILED' } }),
    prisma.backup.findFirst({ orderBy: { createdAt: 'desc' }, where: { status: 'COMPLETED' } }),
    prisma.backupItem.count(),
    prisma.backupSchedule.findMany(),
  ]);

  return { total, completed, failed, latestBackup, totalItems, schedules };
}
