/**
 * Tool Event Handler
 *
 * Integrates the tool logging system with agent run events.
 * When installed via initializeToolEventHandler(), tool executions are
 * automatically logged to agent_run_events when within a run context.
 */

import { randomUUID } from "crypto";
import { setToolLogHandler, type ToolLogEntry } from "@/lib/ai/tool-registry/logging";
import { getRunContext } from "./run-context";
import { appendRunEvent, type AgentRunEventType, type EventLevel } from "./queries";

/**
 * Map to correlate tool start events with their completion events.
 * Key: `${runId}:${toolName}:${startTimestamp}` -> toolRunId
 *
 * This allows us to link tool_started and tool_completed events together.
 */
const activeToolRuns = new Map<string, string>();

/**
 * Map tool log events to agent run event types
 */
function mapEventType(event: ToolLogEntry["event"]): AgentRunEventType {
  switch (event) {
    case "start":
      return "tool_started";
    case "success":
      return "tool_completed";
    case "error":
      return "tool_failed";
    case "retry":
      return "tool_retry";
    default:
      return "tool_started";
  }
}

/**
 * Map tool log levels to event levels
 */
function mapLevel(level: ToolLogEntry["level"]): EventLevel {
  switch (level) {
    case "debug":
      return "debug";
    case "info":
      return "info";
    case "warn":
      return "warn";
    case "error":
      return "error";
    default:
      return "info";
  }
}

/**
 * Generate a cache key for tracking active tool runs
 */
function getToolRunKey(runId: string, toolName: string, sessionId?: string): string {
  // Use sessionId if available to differentiate concurrent runs
  return `${runId}:${toolName}:${sessionId ?? "default"}`;
}

/**
 * The tool log handler that writes to agent_run_events
 *
 * Field population strategy:
 * - toolInvocationId: Generated UUID stored in data.toolInvocationId to correlate start/complete events
 *   NOTE: We cannot use the `toolRunId` column because it has a FK to `tool_runs` table
 * - messageId: Forwarded from RunContext (if available - set when processing a specific message)
 * - stepName: Could be set via metadata if pipeline defines steps
 * - promptVersionId: Not applicable for tools (used for LLM prompt tracking)
 * - llmOperation: Not applicable for tools (used for LLM operations like generate/embed)
 * - toolRunId: Left null (FK to tool_runs table - only set if there's an actual tool_runs record)
 */
async function toolEventHandler(entry: ToolLogEntry): Promise<void> {
  // Get current run context (if any)
  const ctx = getRunContext();

  // Always log to console for visibility
  const prefix = `[Tool:${entry.toolName}]`;
  const duration = entry.durationMs ? ` (${entry.durationMs}ms)` : "";

  switch (entry.event) {
    case "start":
      console.log(`${prefix} Starting execution...`);
      break;
    case "success":
      console.log(`${prefix} Completed successfully${duration}`);
      break;
    case "error":
      console.error(`${prefix} Failed: ${entry.error}${duration}`);
      break;
    case "retry":
      console.warn(`${prefix} Retrying: ${entry.error}`);
      break;
  }

  // If no run context, skip DB logging
  if (!ctx) {
    return;
  }

  // Generate a correlation ID to link start/complete events for the same tool invocation
  // This is stored in data.toolInvocationId (NOT in toolRunId column which has FK to tool_runs)
  let toolInvocationId: string;
  const toolRunKey = getToolRunKey(ctx.runId, entry.toolName, entry.sessionId);

  if (entry.event === "start") {
    // Generate new invocation ID for this tool call
    toolInvocationId = randomUUID();
    activeToolRuns.set(toolRunKey, toolInvocationId);
  } else {
    // Retrieve the invocation ID from the start event
    toolInvocationId = activeToolRuns.get(toolRunKey) ?? randomUUID();

    // Clean up after completion or error (not retry)
    if (entry.event === "success" || entry.event === "error") {
      activeToolRuns.delete(toolRunKey);
    }
  }

  // Prepare event data - includes toolInvocationId for correlation
  const eventData: Record<string, unknown> = {
    toolInvocationId, // Correlation ID to link start/complete events
  };

  if (entry.args) {
    // Summarize args to avoid storing large payloads
    eventData.argKeys = Object.keys(entry.args);
    eventData.argCount = Object.keys(entry.args).length;
  }

  if (entry.result !== undefined) {
    // Store result type/summary, not full result
    eventData.hasResult = true;
    eventData.resultType = typeof entry.result;
    if (typeof entry.result === "object" && entry.result !== null) {
      eventData.resultKeys = Object.keys(entry.result);
    }
  }

  if (entry.error) {
    eventData.error = entry.error;
  }

  if (entry.metadata) {
    eventData.metadata = entry.metadata;
  }

  // Extract stepName from metadata if provided
  const stepName = entry.metadata?.stepName as string | undefined;

  // Append event to run timeline
  // NOTE: toolRunId is intentionally omitted - it has a FK to tool_runs table
  // and we don't have a tool_runs record for these events
  try {
    await appendRunEvent({
      runId: ctx.runId,
      eventType: mapEventType(entry.event),
      level: mapLevel(entry.level),
      durationMs: entry.durationMs,
      toolName: entry.toolName,
      // toolRunId: omitted - FK to tool_runs table, only set if actual tool_runs record exists
      messageId: ctx.messageId,
      pipelineName: ctx.pipelineName,
      stepName,
      data: eventData,
    });
  } catch (error) {
    // Don't let logging failures break tool execution
    console.error("[ToolEventHandler] Failed to append run event:", error);
  }
}

// Keep track of whether handler is installed
let isInitialized = false;

/**
 * Initialize the tool event handler
 * 
 * Call this once at application startup (e.g., in instrumentation.ts or main entry point)
 * to enable automatic tool event logging to agent_run_events.
 */
export function initializeToolEventHandler(): void {
  if (isInitialized) {
    console.log("[ToolEventHandler] Already initialized, skipping");
    return;
  }
  
  setToolLogHandler((entry) => {
    // Fire and forget - don't await in the sync handler
    toolEventHandler(entry).catch((error) => {
      console.error("[ToolEventHandler] Unhandled error:", error);
    });
  });
  
  isInitialized = true;
  console.log("[ToolEventHandler] Initialized - tool events will be logged to agent_run_events");
}

/**
 * Check if the handler is initialized
 */
export function isToolEventHandlerInitialized(): boolean {
  return isInitialized;
}

