/**
 * Save Points System — "The Video Game Rule"
 * 
 * Creates automatic snapshots before any destructive or large-scale change.
 * Every agent run is treated like a video game — you always save before the boss fight.
 * 
 * Reference: Nate B Jones — "5 Management Skills for AI Agent Builders"
 * Skill #1: Save Points and Version Control
 */

import { prisma } from '@/lib/db';
import { EventBus } from '../event-bus';

export interface SavePoint {
  id: string;
  name: string;
  trigger: 'pre_operation' | 'manual' | 'auto_checkpoint';
  entityTypes: string[];
  entityCounts: Record<string, number>;
  totalItems: number;
  operationContext: {
    operationType: string;
    targetEntity: string;
    agentId?: string;
    sessionId?: string;
    description: string;
  };
  createdAt: Date;
}

export interface SavePointResult {
  success: boolean;
  savePointId: string | null;
  error?: string;
}

/**
 * Create a save-point before a destructive operation.
 * 
 * Standing Order 2.1: "Create an automatic save-point BEFORE any
 * destructive or large-scale change."
 * 
 * Standing Order 2.4: "NEVER proceed with a write/delete if the
 * save-point creation fails."
 */
export async function createSavePoint(context: {
  operationType: string;
  targetEntity: string;
  targetIds?: string[];
  agentId?: string;
  sessionId?: string;
  description: string;
}): Promise<SavePointResult> {
  const name = `pre-op-${context.operationType}-${context.targetEntity}-${Date.now()}`;

  try {
    // Determine which entity types to snapshot
    const entityType = context.targetEntity;
    const itemsToBackup: Array<{ entityType: string; entityId: string; data: unknown }> = [];

    // If specific IDs provided, snapshot those records
    if (context.targetIds && context.targetIds.length > 0) {
      for (const id of context.targetIds) {
        const record = await fetchEntityById(entityType, id);
        if (record) {
          itemsToBackup.push({
            entityType,
            entityId: id,
            data: record,
          });
        }
      }
    } else {
      // Snapshot recent records of the target type (up to 100 for safety)
      const records = await fetchRecentEntities(entityType, 100);
      for (const record of records) {
        const recObj = record as Record<string, unknown>;
        itemsToBackup.push({
          entityType,
          entityId: (recObj.id as string) || 'unknown',
          data: record,
        });
      }
    }

    // Create the backup record
    const backup = await prisma.backup.create({
      data: {
        name,
        type: 'SNAPSHOT',
        status: 'COMPLETED',
        trigger: 'PRE_OPERATION',
        entityTypes: JSON.parse(JSON.stringify([entityType])),
        entityCounts: JSON.parse(JSON.stringify({ [entityType]: itemsToBackup.length })),
        totalItems: itemsToBackup.length,
        createdByAgent: context.agentId || 'system',
        notes: `[SavePoint] ${context.description}`,
      },
    });

    // Store individual items
    if (itemsToBackup.length > 0) {
      await prisma.backupItem.createMany({
        data: itemsToBackup.map(item => ({
          backupId: backup.id,
          entityType: item.entityType,
          entityId: item.entityId,
          data: JSON.parse(JSON.stringify(item.data)),
        })),
      });
    }

    // Emit event
    await EventBus.emit('system', 'gfs.supervision.save_point_created', {
      savePointId: backup.id,
      name,
      entityType,
      itemCount: itemsToBackup.length,
      trigger: context.operationType,
    });

    console.log(`[SavePoint] Created: ${backup.id} (${itemsToBackup.length} items of type ${entityType})`);

    return { success: true, savePointId: backup.id };
  } catch (error) {
    const errorMsg = error instanceof Error ? error.message : 'Unknown error';
    console.error(`[SavePoint] FAILED to create save-point: ${errorMsg}`);

    // Standing Order 2.4 — if save-point fails, the operation MUST NOT proceed
    return { success: false, savePointId: null, error: errorMsg };
  }
}

/**
 * Restore from a save-point (rollback)
 */
export async function restoreFromSavePoint(savePointId: string): Promise<{
  success: boolean;
  restoredCount: number;
  error?: string;
}> {
  try {
    const backup = await prisma.backup.findUnique({
      where: { id: savePointId },
      include: { items: true },
    });

    if (!backup) {
      return { success: false, restoredCount: 0, error: 'Save-point not found' };
    }

    let restoredCount = 0;
    for (const item of backup.items) {
      try {
        await restoreEntity(item.entityType, item.entityId, item.data as Record<string, unknown>);
        restoredCount++;
      } catch (err) {
        console.error(`[SavePoint] Failed to restore ${item.entityType}:${item.entityId}:`, err);
      }
    }

    await EventBus.emit('system', 'gfs.supervision.save_point_restored', {
      savePointId,
      restoredCount,
      totalItems: backup.items.length,
    });

    return { success: true, restoredCount };
  } catch (error) {
    return {
      success: false,
      restoredCount: 0,
      error: error instanceof Error ? error.message : 'Unknown error',
    };
  }
}

// === Helpers ===

async function fetchEntityById(entityType: string, id: string): Promise<unknown | null> {
  const modelMap: Record<string, () => Promise<unknown>> = {
    agent: () => prisma.agent.findUnique({ where: { id } }),
    task: () => prisma.task.findUnique({ where: { id } }),
    workflow: () => prisma.workflow.findUnique({ where: { id } }),
    event: () => prisma.event.findUnique({ where: { id } }),
    integration: () => prisma.integration.findUnique({ where: { id } }),
    knowledgeNode: () => prisma.knowledgeNode.findUnique({ where: { id } }),
  };

  const fetcher = modelMap[entityType];
  if (!fetcher) {
    console.warn(`[SavePoint] Unknown entity type: ${entityType}`);
    return null;
  }
  return fetcher();
}

async function fetchRecentEntities(entityType: string, limit: number): Promise<unknown[]> {
  const modelMap: Record<string, () => Promise<unknown[]>> = {
    agent: () => prisma.agent.findMany({ take: limit, orderBy: { createdAt: 'desc' } }),
    task: () => prisma.task.findMany({ take: limit, orderBy: { createdAt: 'desc' } }),
    workflow: () => prisma.workflow.findMany({ take: limit, orderBy: { createdAt: 'desc' } }),
    event: () => prisma.event.findMany({ take: limit, orderBy: { timestamp: 'desc' } }),
    integration: () => prisma.integration.findMany({ take: limit, orderBy: { createdAt: 'desc' } }),
  };

  const fetcher = modelMap[entityType];
  if (!fetcher) return [];
  return fetcher();
}

async function restoreEntity(
  entityType: string,
  entityId: string,
  data: Record<string, unknown>
): Promise<void> {
  // Remove system fields that shouldn't be upserted
  const { id, createdAt, updatedAt, ...restData } = data;
  const cleanData = JSON.parse(JSON.stringify(restData));

  const modelMap: Record<string, () => Promise<unknown>> = {
    agent: () =>
      prisma.agent.upsert({
        where: { id: entityId },
        create: { id: entityId, ...cleanData },
        update: cleanData,
      }),
    task: () =>
      prisma.task.upsert({
        where: { id: entityId },
        create: { id: entityId, ...cleanData },
        update: cleanData,
      }),
  };

  const restorer = modelMap[entityType];
  if (restorer) {
    await restorer();
  } else {
    console.warn(`[SavePoint] No restore handler for entity type: ${entityType}`);
  }
}
