/**
 * GFS Agent Executor - Makes agents think and act
 * 
 * Enhanced with the 5 Core Supervision Skills + RAG:
 * 1. Save-points before every write/delete
 * 2. Context hygiene checks per session
 * 3. Standing orders injected into every prompt
 * 4. Blast-radius enforcement on task scope
 * 5. Pre-answered safety questions in system prompt
 * 6. RAG-grounded context retrieval before every decision
 */

import { prisma } from '@/lib/db';
import { callLLM, buildAgentSystemPrompt, LLMMessage } from './llm-client';
import {
  createSavePoint,
  checkBlastRadius,
  getStandingOrdersPrompt,
  getPreAnsweredQuestionsPrompt,
} from '../supervision';
import { retrieveContext } from '../rag';

export interface ExecutionResult {
  success: boolean;
  output: unknown;
  error?: string;
  savePointId?: string | null;
  supervisionLog?: string[];
  metrics: {
    startedAt: Date;
    completedAt: Date;
    durationMs: number;
    tokensUsed: number;
  };
}

export async function executeTask(taskId: string): Promise<ExecutionResult> {
  const startedAt = new Date();
  const supervisionLog: string[] = [];

  try {
    // Load task with agent
    const task = await prisma.task.findUnique({
      where: { id: taskId },
      include: { agent: true },
    });

    if (!task) {
      throw new Error(`Task ${taskId} not found`);
    }

    if (!task.agent) {
      throw new Error(`Task ${taskId} has no assigned agent`);
    }

    // === SUPERVISION: Blast-radius check ===
    const taskInput = task.input as Record<string, unknown> || {};
    const scopeCheck = checkBlastRadius({
      type: task.type,
      entityType: task.type,
      entityCount: 1,
      description: JSON.stringify(taskInput).slice(0, 200),
    });
    if (!scopeCheck.allowed) {
      supervisionLog.push(`[BLAST-RADIUS] Violations: ${scopeCheck.violations.join('; ')}`);
      throw new Error(`Task blocked by blast-radius policy: ${scopeCheck.violations[0]}`);
    }
    supervisionLog.push('[BLAST-RADIUS] Scope check passed');

    // === SUPERVISION: Save-point before write operations ===
    const writeTypes = ['create', 'update', 'delete', 'deploy', 'modify', 'execute'];
    const isWriteOp = writeTypes.some(wt => task.type.toLowerCase().includes(wt));
    let savePointId: string | null = null;

    if (isWriteOp) {
      const sp = await createSavePoint({
        operationType: task.type,
        targetEntity: 'task',
        targetIds: [taskId],
        agentId: task.agent.id,
        description: `Pre-execution save-point for task ${taskId} (${task.type})`,
      });
      if (!sp.success) {
        supervisionLog.push(`[SAVE-POINT] FAILED: ${sp.error}`);
        throw new Error(`Save-point creation failed — operation halted per Standing Order 2.4: ${sp.error}`);
      }
      savePointId = sp.savePointId;
      supervisionLog.push(`[SAVE-POINT] Created: ${savePointId}`);
    }

    // Update task status to running
    await prisma.task.update({
      where: { id: taskId },
      data: { status: 'running', startedAt },
    });

    // === RAG: Retrieve relevant business context ===
    const ragQuery = `${task.type} ${JSON.stringify(taskInput).slice(0, 300)}`;
    const ragContext = retrieveContext(ragQuery);
    supervisionLog.push(
      `[RAG] Retrieved ${ragContext.chunkCount} chunks from ${ragContext.citations.length} sources`
    );

    // Build messages WITH standing orders + pre-answered questions + RAG context
    const agentSystemPrompt = buildAgentSystemPrompt(task.agent);
    const standingOrders = getStandingOrdersPrompt();
    const preAnswered = getPreAnsweredQuestionsPrompt();
    const systemPrompt = `${agentSystemPrompt}\n\n${standingOrders}\n\n${preAnswered}`;

    const messages: LLMMessage[] = [
      { role: 'system', content: systemPrompt },
      {
        role: 'user',
        content: [
          ragContext.chunkCount > 0 ? ragContext.contextText : '',
          '',
          `Execute the following task:`,
          '',
          `Task Type: ${task.type}`,
          `Input: ${JSON.stringify(taskInput, null, 2)}`,
          '',
          `Provide your response in a structured format.`,
          ragContext.chunkCount > 0
            ? 'Cite retrieved business context where relevant.'
            : '',
        ].filter(Boolean).join('\n'),
      },
    ];
    supervisionLog.push('[STANDING-ORDERS] Injected into system prompt');
    supervisionLog.push('[PRE-ANSWERED] Safety rules injected into system prompt');

    // Call LLM
    const response = await callLLM(messages, {
      temperature: 0.5,
      maxTokens: 2000,
    });

    const completedAt = new Date();
    const durationMs = completedAt.getTime() - startedAt.getTime();

    // Parse output (try JSON first, fall back to text)
    let output: unknown;
    try {
      output = JSON.parse(response.content);
    } catch {
      output = { response: response.content };
    }

    // Update task as completed
    await prisma.task.update({
      where: { id: taskId },
      data: {
        status: 'completed',
        output: JSON.parse(JSON.stringify(output)),
        completedAt,
      },
    });

    // Update agent metrics
    const agentMetrics = (task.agent.metrics as Record<string, number>) || {};
    await prisma.agent.update({
      where: { id: task.agent.id },
      data: {
        status: 'idle',
        metrics: JSON.parse(JSON.stringify({
          ...agentMetrics,
          tasksCompleted: (agentMetrics.tasksCompleted || 0) + 1,
          totalTokens: (agentMetrics.totalTokens || 0) + response.usage.totalTokens,
          avgResponseTime: Math.round(
            ((agentMetrics.avgResponseTime || 0) * (agentMetrics.tasksCompleted || 0) + durationMs) /
              ((agentMetrics.tasksCompleted || 0) + 1)
          ),
        })),
      },
    });

    supervisionLog.push(`[COMPLETE] Task ${taskId} finished in ${durationMs}ms`);

    return {
      success: true,
      output,
      savePointId,
      supervisionLog,
      metrics: {
        startedAt,
        completedAt,
        durationMs,
        tokensUsed: response.usage.totalTokens,
      },
    };
  } catch (error) {
    const completedAt = new Date();
    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
    supervisionLog.push(`[ERROR] ${errorMessage}`);

    // Update task as failed (safe — only if task exists)
    try {
      await prisma.task.update({
        where: { id: taskId },
        data: {
          status: 'failed',
          output: JSON.parse(JSON.stringify({ error: errorMessage, supervisionLog })),
          completedAt,
        },
      });
    } catch {
      // Task may not exist yet if we failed early
    }

    return {
      success: false,
      output: null,
      error: errorMessage,
      supervisionLog,
      metrics: {
        startedAt,
        completedAt,
        durationMs: completedAt.getTime() - startedAt.getTime(),
        tokensUsed: 0,
      },
    };
  }
}

export async function spawnAgentFromTemplate(
  templateId: string,
  overrides?: { name?: string; config?: Record<string, unknown> }
) {
  const template = await prisma.agentTemplate.findUnique({
    where: { id: templateId },
  });

  if (!template) {
    throw new Error(`Template ${templateId} not found`);
  }

  const capabilities = template.capabilities as unknown[];
  const personalityConfig = template.personalityConfig as Record<string, unknown>;
  const defaultConfig = template.defaultConfig as Record<string, unknown> | null;

  const agent = await prisma.agent.create({
    data: {
      name: overrides?.name || `${template.name}-${Date.now()}`,
      description: template.description,
      type: template.type.toLowerCase(),
      capabilities: JSON.parse(JSON.stringify(capabilities)),
      personality: JSON.parse(JSON.stringify(personalityConfig)),
      config: overrides?.config 
        ? JSON.parse(JSON.stringify(overrides.config)) 
        : (defaultConfig ? JSON.parse(JSON.stringify(defaultConfig)) : undefined),
      status: 'idle',
      createdBy: 'factory',
    },
  });

  return agent;
}
