/**
 * Orchestration Tools - MCP tools for Chrome-Sisyphus workflow management
 */

import * as dns from 'dns';
import { promisify } from 'util';
import { MCPServer, getMCPServer } from '../mcp-server';
import { MCPToolDefinition, MCPResult, ToolHandler } from '../types/mcp';
import { TOOL_ANNOTATIONS } from '../types/tool-annotations';
import { getWorkflowEngine, WorkflowDefinition } from '../orchestration/workflow-engine';
import { filterToolsForWorker, WorkerToolConfig } from '../types/tool-manifest';
import { getPlanRegistry } from '../orchestration/plan-registry';
import { PlanExecutor } from '../orchestration/plan-executor';
import { formatError } from '../utils/format-error';
import { validateBrowserTaskSignature } from '../contracts/task-signature';
import {
  buildReflectionStrategyMetadata,
  parseReflectionStrategy,
} from '../orchestration/reflection-strategy';
import { getTaskDriftLedger } from '../harness/task-ledger';

const dnsResolve = promisify(dns.resolve);

// ============================================
// workflow_init - Initialize a new workflow
// ============================================

const workflowInitDefinition: MCPToolDefinition = {
  name: 'workflow_init',
  description: 'Initialize a workflow with multiple isolated workers for parallel browser ops.',
  inputSchema: {
    type: 'object',
    properties: {
      name: {
        type: 'string',
        description: 'Workflow name',
      },
      workers: {
        type: 'array',
        description: 'List of workers to create',
        items: {
          type: 'object',
          properties: {
            name: {
              type: 'string',
              description: 'Worker name',
            },
            url: {
              type: 'string',
              description: 'Initial URL to navigate to',
            },
            task: {
              type: 'string',
              description: 'Task description for the worker',
            },
            successCriteria: {
              type: 'string',
              description: 'Criteria for task completion',
            },
            shareCookies: {
              type: 'boolean',
              description: 'Share cookies from Chrome session. Default: false',
            },
          },
          required: ['name', 'url', 'task'],
        },
      },
      workerTimeoutMs: {
        type: 'number',
        description: 'Per-worker timeout in ms. Default: 60000',
      },
      maxStaleIterations: {
        type: 'number',
        description: 'Stale update limit before circuit break. Default: 5',
      },
      globalTimeoutMs: {
        type: 'number',
        description: 'Global workflow timeout in ms. Default: 300000',
      },
    },
    required: ['name', 'workers'],
  },
  annotations: TOOL_ANNOTATIONS.workflow_init,
};

const workflowInitHandler: ToolHandler = async (
  sessionId: string,
  args: Record<string, unknown>
): Promise<MCPResult> => {
  const engine = getWorkflowEngine();
  const name = args.name as string;
  const workerTimeoutMs = args.workerTimeoutMs as number | undefined;
  const maxStaleIterations = args.maxStaleIterations as number | undefined;
  const globalTimeoutMs = args.globalTimeoutMs as number | undefined;
  const workerDefs = args.workers as Array<{
    name: string;
    url: string;
    task: string;
    successCriteria?: string;
    shareCookies?: boolean;
  }>;

  // DNS pre-resolution: resolve all worker hostnames in parallel
  // This saves ~200ms per site by warming the DNS cache before navigation
  const uniqueHostnames = [...new Set(
    workerDefs
      .map(w => {
        try { return new URL(w.url.startsWith('http') ? w.url : `https://${w.url}`).hostname; }
        catch { return null; }
      })
      .filter((h): h is string => h !== null)
  )];

  if (uniqueHostnames.length > 0) {
    await Promise.allSettled(
      uniqueHostnames.map(hostname => dnsResolve(hostname).catch(() => {}))
    );
  }

  try {
    // Note: preWarmForWorkflow removed — acquireBatch() in workflow-engine already
    // handles on-demand page creation. Pre-warming was redundant and caused
    // about:blank ghost tabs via pool replenishment.

    // Create workflow definition
    const workflow: WorkflowDefinition = {
      id: `wf-${Date.now()}`,
      name,
      steps: workerDefs.map((w) => {
        if (w.shareCookies === undefined) {
          console.error(`[Orchestration] Worker "${w.name}": shareCookies not specified, defaulting to true (shared context for faster init)`);
        }
        return {
          workerId: `worker-${w.name}`,
          workerName: w.name,
          url: w.url,
          task: w.task,
          successCriteria: w.successCriteria || 'Task completed successfully',
          shareCookies: w.shareCookies ?? true,  // Default to shared cookies for faster context creation
        };
      }),
      parallel: true,
      maxRetries: 3,
      timeout: workerTimeoutMs || 60000,
      maxStaleIterations: maxStaleIterations || 5,
      globalTimeoutMs: globalTimeoutMs || 300000,
    };

    // Initialize workflow
    const result = await engine.initWorkflow(sessionId, workflow);

    // Generate tool manifest for worker agents (Shared Tool Registry)
    // Workers receive pre-loaded tool schemas so they can skip ToolSearch calls
    let manifestTools;
    try {
      const mcpServer = getMCPServer();
      const manifest = mcpServer.getToolManifest();
      const workerToolConfig: WorkerToolConfig = { workerType: 'extraction' };
      manifestTools = filterToolsForWorker(manifest, workerToolConfig);
      console.error(`[Orchestration] Tool manifest generated: ${manifestTools.length} tools for extraction workers (v${manifest.version})`);
    } catch (err) {
      console.error(`[Orchestration] Tool manifest generation failed (non-fatal, using fallback): ${err instanceof Error ? err.message : String(err)}`);
      manifestTools = undefined;
    }

    // Generate worker prompts for reference
    const workerPrompts = result.workers.map((w, i) => ({
      workerName: w.workerName,
      tabId: w.tabId,
      prompt: engine.generateWorkerPrompt(
        w.workerId,
        w.workerName,
        w.tabId,
        workflow.steps[i].task,
        workflow.steps[i].successCriteria,
        manifestTools,
        workflow.steps[i].url
      ),
    }));

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(
            {
              orchestrationId: result.orchestrationId,
              status: 'INITIALIZED',
              workers: result.workers.map((w, i) => ({
                workerId: w.workerId,
                workerName: w.workerName,
                tabId: w.tabId,
                task: workflow.steps[i].task,
              })),
              scratchpadDir: '.agent/chrome-sisyphus',
              message: `Workflow initialized with ${result.workers.length} workers. Launch Background Tasks for each worker using the Task tool with run_in_background: true.`,
              workerPrompts,
            },
            null,
            2
          ),
        },
      ],
    };
  } catch (error) {
    return {
      content: [
        {
          type: 'text',
          text: `Error initializing workflow: ${formatError(error)}`,
        },
      ],
      isError: true,
    };
  }
};

// ============================================
// workflow_status - Get workflow status
// ============================================

const workflowStatusDefinition: MCPToolDefinition = {
  name: 'workflow_status',
  description: 'Get current workflow status and worker states.',
  inputSchema: {
    type: 'object',
    properties: {
      includeWorkerDetails: {
        type: 'boolean',
        description: 'Include worker scratchpad details. Default: false',
      },
      includeLedger: {
        type: 'boolean',
        description: 'Include compact task drift ledger diagnostics. Default: false',
      },
    },
    required: [],
  },
  annotations: TOOL_ANNOTATIONS.workflow_status,
};

const workflowStatusHandler: ToolHandler = async (
  sessionId: string,
  args: Record<string, unknown>
): Promise<MCPResult> => {
  const engine = getWorkflowEngine();
  const includeWorkerDetails = args.includeWorkerDetails as boolean ?? false;
  const includeLedger = args.includeLedger as boolean ?? false;

  try {
    const orch = await engine.getOrchestrationStatus();
    if (!orch) {
      const noWorkflow: Record<string, unknown> = { status: 'NO_WORKFLOW', message: 'No active workflow found' };
      if (includeLedger) {
        noWorkflow.taskLedger = getTaskDriftLedger().snapshot(sessionId);
      }
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(noWorkflow),
          },
        ],
      };
    }

    const result: Record<string, unknown> = {
      orchestrationId: orch.orchestrationId,
      status: orch.status,
      task: orch.task,
      workers: orch.workers,
      completedWorkers: orch.completedWorkers,
      failedWorkers: orch.failedWorkers,
      duration: Date.now() - orch.createdAt,
    };

    if (includeWorkerDetails) {
      const workerStates = await engine.getAllWorkerStates();
      result.workerDetails = workerStates.map(w => ({
        workerName: w.workerName,
        status: w.status,
        iteration: w.iteration,
        progressLog: w.progressLog,
        extractedData: w.extractedData,
        errors: w.errors,
      }));
    }

    if (includeLedger) {
      result.taskLedger = getTaskDriftLedger().snapshot(sessionId);
    }

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  } catch (error) {
    return {
      content: [
        {
          type: 'text',
          text: `Error getting workflow status: ${formatError(error)}`,
        },
      ],
      isError: true,
    };
  }
};

// ============================================
// workflow_collect - Collect workflow results
// ============================================

const workflowCollectDefinition: MCPToolDefinition = {
  name: 'workflow_collect',
  description: 'Collect and aggregate results from all workers after completion.',
  inputSchema: {
    type: 'object',
    properties: {},
    required: [],
  },
  annotations: TOOL_ANNOTATIONS.workflow_collect,
};

const workflowCollectHandler: ToolHandler = async (
  _sessionId: string,
  _args: Record<string, unknown>
): Promise<MCPResult> => {
  const engine = getWorkflowEngine();

  try {
    const results = await engine.collectResults();
    if (!results) {
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({ status: 'NO_RESULTS', message: 'No workflow results found' }),
          },
        ],
      };
    }

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(results, null, 2),
        },
      ],
    };
  } catch (error) {
    return {
      content: [
        {
          type: 'text',
          text: `Error collecting results: ${formatError(error)}`,
        },
      ],
      isError: true,
    };
  }
};

// ============================================
// workflow_cleanup - Cleanup workflow resources
// ============================================

const workflowCleanupDefinition: MCPToolDefinition = {
  name: 'workflow_cleanup',
  description: 'Clean up workflow resources (workers, tabs, scratchpads).',
  inputSchema: {
    type: 'object',
    properties: {},
    required: [],
  },
  annotations: TOOL_ANNOTATIONS.workflow_cleanup,
};

const workflowCleanupHandler: ToolHandler = async (
  sessionId: string,
  _args: Record<string, unknown>
): Promise<MCPResult> => {
  const engine = getWorkflowEngine();

  try {
    await engine.cleanupWorkflow(sessionId);

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify({
            status: 'CLEANED',
            message: 'Workflow resources cleaned up successfully',
          }),
        },
      ],
    };
  } catch (error) {
    return {
      content: [
        {
          type: 'text',
          text: `Error cleaning up workflow: ${formatError(error)}`,
        },
      ],
      isError: true,
    };
  }
};

// ============================================
// worker_update - Update worker progress
// ============================================

const workerUpdateDefinition: MCPToolDefinition = {
  name: 'worker_update',
  description: 'Report worker progress to the orchestration scratchpad.',
  inputSchema: {
    type: 'object',
    properties: {
      workerName: {
        type: 'string',
        description: 'Name of the worker',
      },
      status: {
        type: 'string',
        enum: ['INIT', 'IN_PROGRESS', 'SUCCESS', 'PARTIAL', 'FAIL'],
        description: 'Worker status',
      },
      iteration: {
        type: 'number',
        description: 'Current iteration number',
      },
      action: {
        type: 'string',
        description: 'Action being performed',
      },
      result: {
        type: 'string',
        enum: ['SUCCESS', 'FAIL', 'IN_PROGRESS'],
        description: 'Result of the action',
      },
      extractedData: {
        type: 'object',
        description: 'Data extracted so far',
        properties: {},
        additionalProperties: true,
      },
      error: {
        type: 'string',
        description: 'Error message if any',
      },
    },
    required: ['workerName'],
  },
  annotations: TOOL_ANNOTATIONS.worker_update,
};

const workerUpdateHandler: ToolHandler = async (
  _sessionId: string,
  args: Record<string, unknown>
): Promise<MCPResult> => {
  const engine = getWorkflowEngine();
  const workerName = args.workerName as string;

  try {
    await engine.updateWorkerProgress(workerName, {
      status: args.status as 'INIT' | 'IN_PROGRESS' | 'SUCCESS' | 'PARTIAL' | 'FAIL' | undefined,
      iteration: args.iteration as number | undefined,
      action: args.action as string | undefined,
      result: args.result as 'SUCCESS' | 'FAIL' | 'IN_PROGRESS' | undefined,
      extractedData: args.extractedData,
      error: args.error as string | undefined,
    });

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify({
            status: 'UPDATED',
            workerName,
            message: `Worker ${workerName} progress updated`,
          }),
        },
      ],
    };
  } catch (error) {
    return {
      content: [
        {
          type: 'text',
          text: `Error updating worker: ${formatError(error)}`,
        },
      ],
      isError: true,
    };
  }
};

// ============================================
// worker_complete - Mark worker as complete
// ============================================

const workerCompleteDefinition: MCPToolDefinition = {
  name: 'worker_complete',
  description: 'Mark a worker as complete with final results.',
  inputSchema: {
    type: 'object',
    properties: {
      workerName: {
        type: 'string',
        description: 'Name of the worker',
      },
      status: {
        type: 'string',
        enum: ['SUCCESS', 'PARTIAL', 'FAIL'],
        description: 'Final status',
      },
      resultSummary: {
        type: 'string',
        description: 'Result summary (max 100 chars)',
      },
      extractedData: {
        type: 'object',
        description: 'Final extracted data',
        properties: {},
        additionalProperties: true,
      },
    },
    required: ['workerName', 'status', 'resultSummary'],
  },
  annotations: TOOL_ANNOTATIONS.worker_complete,
};

const workerCompleteHandler: ToolHandler = async (
  _sessionId: string,
  args: Record<string, unknown>
): Promise<MCPResult> => {
  const engine = getWorkflowEngine();
  const workerName = args.workerName as string;
  const status = args.status as 'SUCCESS' | 'PARTIAL' | 'FAIL';
  const resultSummary = args.resultSummary as string;
  const extractedData = args.extractedData;

  try {
    await engine.completeWorker(workerName, status, resultSummary, extractedData);

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify({
            status: 'COMPLETED',
            workerName,
            workerStatus: status,
            message: `Worker ${workerName} marked as ${status}`,
          }),
        },
      ],
    };
  } catch (error) {
    return {
      content: [
        {
          type: 'text',
          text: `Error completing worker: ${formatError(error)}`,
        },
      ],
      isError: true,
    };
  }
};

// ============================================
// workflow_collect_partial - Collect completed results without waiting
// ============================================

const workflowCollectPartialDefinition: MCPToolDefinition = {
  name: 'workflow_collect_partial',
  description: 'Collect results from completed workers without waiting for all to finish.',
  inputSchema: {
    type: 'object',
    properties: {
      onlySuccessful: {
        type: 'boolean',
        description: 'Only return successful workers. Default: false',
      },
    },
    required: [],
  },
  annotations: TOOL_ANNOTATIONS.workflow_collect_partial,
};

const workflowCollectPartialHandler: ToolHandler = async (
  _sessionId: string,
  args: Record<string, unknown>
): Promise<MCPResult> => {
  const engine = getWorkflowEngine();
  const onlySuccessful = args.onlySuccessful as boolean ?? false;

  try {
    const orch = await engine.getOrchestrationStatus();
    if (!orch) {
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({ status: 'NO_WORKFLOW', message: 'No active workflow found' }),
          },
        ],
      };
    }

    // Get all worker states
    const workerStates = await engine.getAllWorkerStates();

    // Filter to completed workers only
    const completedStatuses = onlySuccessful
      ? ['SUCCESS', 'PARTIAL']
      : ['SUCCESS', 'PARTIAL', 'FAIL'];

    const completedWorkers = workerStates.filter(w =>
      completedStatuses.includes(w.status)
    );

    const pendingWorkers = workerStates.filter(w =>
      !['SUCCESS', 'PARTIAL', 'FAIL'].includes(w.status)
    );

    const results = {
      orchestrationId: orch.orchestrationId,
      overallStatus: orch.status,
      progress: {
        total: orch.workers.length,
        completed: orch.completedWorkers,
        failed: orch.failedWorkers,
        pending: orch.workers.length - orch.completedWorkers - orch.failedWorkers,
      },
      completedWorkers: completedWorkers.map(w => ({
        workerName: w.workerName,
        status: w.status,
        extractedData: w.extractedData,
        iterations: w.iteration,
        errors: w.errors,
      })),
      pendingWorkerNames: pendingWorkers.map(w => w.workerName),
      duration: Date.now() - orch.createdAt,
    };

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(results, null, 2),
        },
      ],
    };
  } catch (error) {
    return {
      content: [
        {
          type: 'text',
          text: `Error collecting partial results: ${formatError(error)}`,
        },
      ],
      isError: true,
    };
  }
};

// ============================================
// execute_plan - Execute a cached compiled plan
// ============================================

const executePlanDefinition: MCPToolDefinition = {
  name: 'execute_plan',
  description: 'Execute a cached plan by ID, bypassing per-step LLM calls. Falls back gracefully on failure for manual retry.',
  inputSchema: {
    type: 'object',
    properties: {
      planId: {
        type: 'string',
        description: 'Plan ID to execute',
      },
      tabId: {
        type: 'string',
        description: 'Tab ID to execute the plan against',
      },
      params: {
        type: 'object',
        description: 'Runtime params merged with plan defaults',
        properties: {},
        additionalProperties: true,
      },
      taskSignature: {
        type: 'object',
        description: 'Optional deterministic BrowserTaskSignature that bounds allowed tools, loop guards, and budgets for this execution',
        properties: {},
        additionalProperties: true,
      },
      reflectionStrategy: {
        type: 'string',
        enum: ['none', 'last_attempt', 'reflection', 'last_attempt_and_reflection'],
        description: 'Opt-in bounded reflection metadata strategy. Default omitted path preserves legacy output.',
      },
      reflectionScope: {
        type: 'object',
        description: 'Optional reflection recall scope: domain, taskFingerprint, contractId.',
        properties: {},
        additionalProperties: true,
      },
    },
    required: ['planId', 'tabId'],
  },
  annotations: TOOL_ANNOTATIONS.execute_plan,
};

const executePlanHandler: ToolHandler = async (
  sessionId: string,
  args: Record<string, unknown>
): Promise<MCPResult> => {
  const planId = args.planId as string;
  const tabId = args.tabId as string;
  const runtimeParams = (args.params as Record<string, unknown>) || {};
  const rawTaskSignature = args.taskSignature;
  const rawReflectionStrategy = args.reflectionStrategy;

  if (!planId || !tabId) {
    return {
      content: [{ type: 'text', text: 'Error: planId and tabId are required' }],
      isError: true,
    };
  }

  const reflectionStrategyResult = parseReflectionStrategy(rawReflectionStrategy);
  if (!reflectionStrategyResult.ok) {
    return {
      content: [{ type: 'text', text: JSON.stringify({ status: 'INVALID_REFLECTION_STRATEGY', error: reflectionStrategyResult.error }) }],
      isError: true,
    };
  }

  try {
    const registry = getPlanRegistry();
    const entry = registry.getEntry(planId);

    if (!entry) {
      return {
        content: [{
          type: 'text',
          text: JSON.stringify({
            status: 'PLAN_NOT_FOUND',
            planId,
            availablePlans: registry.getEntries().map(e => e.id),
            message: `No plan found with ID "${planId}". Use workflow_init for manual execution.`,
          }),
        }],
        isError: true,
      };
    }

    // Check confidence threshold
    if (entry.confidence < entry.minConfidenceToUse) {
      return {
        content: [{
          type: 'text',
          text: JSON.stringify({
            status: 'LOW_CONFIDENCE',
            planId,
            confidence: entry.confidence,
            threshold: entry.minConfidenceToUse,
            message: `Plan confidence (${entry.confidence.toFixed(2)}) is below threshold (${entry.minConfidenceToUse}). Use manual execution.`,
          }),
        }],
        isError: true,
      };
    }

    const plan = registry.loadPlan(entry);
    if (!plan) {
      return {
        content: [{
          type: 'text',
          text: JSON.stringify({
            status: 'PLAN_LOAD_FAILED',
            planId,
            message: `Failed to load plan file for "${planId}".`,
          }),
        }],
        isError: true,
      };
    }

    const taskSignatureResult = rawTaskSignature === undefined
      ? null
      : validateBrowserTaskSignature(rawTaskSignature);
    if (taskSignatureResult && !taskSignatureResult.ok) {
      return {
        content: [{
          type: 'text',
          text: JSON.stringify({
            status: 'INVALID_TASK_SIGNATURE',
            planId,
            errors: taskSignatureResult.errors,
            message: 'taskSignature failed deterministic schema validation.',
          }),
        }],
        isError: true,
      };
    }

    // Create executor with MCPServer's tool resolver
    const mcpServer = getMCPServer();
    const executor = new PlanExecutor((toolName: string) => mcpServer.getToolHandler(toolName));

    // Execute the plan
    const mergedParams = { tabId, ...runtimeParams };
    const result = await executor.execute(
      plan,
      sessionId,
      mergedParams,
      taskSignatureResult?.ok ? { taskSignature: taskSignatureResult.value } : {}
    );

    // Update stats
    registry.updateStats(planId, result.success, result.durationMs, result.failure?.class);
    const reflectionStrategy = rawReflectionStrategy === undefined
      ? undefined
      : buildReflectionStrategyMetadata({
          strategy: reflectionStrategyResult.value,
          planId,
          params: runtimeParams,
          scope: args.reflectionScope as { domain?: string; taskFingerprint?: string; contractId?: string } | undefined,
        });

    return {
      content: [{
        type: 'text',
        text: JSON.stringify({
          status: result.success ? 'SUCCESS' : 'FAILED',
          planId: result.planId,
          stepsExecuted: result.stepsExecuted,
          totalSteps: result.totalSteps,
          durationMs: result.durationMs,
          data: result.data,
          error: result.error,
          failure: result.failure,
          recoveryCandidates: result.recoveryCandidates,
          evidence: result.evidence,
          ...(result.taskSignature ? { taskSignature: result.taskSignature } : {}),
          ...(reflectionStrategy ? { reflectionStrategy } : {}),
          message: result.success
            ? `Plan "${planId}" executed successfully in ${result.durationMs}ms (${result.stepsExecuted}/${result.totalSteps} steps)`
            : `Plan "${planId}" failed: ${result.error}. Consider manual execution.`,
        }, null, 2),
      }],
      isError: !result.success,
    };
  } catch (error) {
    return {
      content: [{
        type: 'text',
        text: `Error executing plan "${planId}": ${formatError(error)}`,
      }],
      isError: true,
    };
  }
};

// ============================================
// Register all orchestration tools
// ============================================

export function registerOrchestrationTools(server: MCPServer): void {
  server.registerTool('workflow_init', workflowInitHandler, workflowInitDefinition);
  server.registerTool('workflow_status', workflowStatusHandler, workflowStatusDefinition);
  server.registerTool('workflow_collect', workflowCollectHandler, workflowCollectDefinition);
  server.registerTool('workflow_collect_partial', workflowCollectPartialHandler, workflowCollectPartialDefinition);
  server.registerTool('workflow_cleanup', workflowCleanupHandler, workflowCleanupDefinition);
  server.registerTool('worker_update', workerUpdateHandler, workerUpdateDefinition);
  server.registerTool('worker_complete', workerCompleteHandler, workerCompleteDefinition);
  server.registerTool('execute_plan', executePlanHandler, executePlanDefinition);

  console.error('[Orchestration] Registered 8 orchestration tools (including execute_plan)');
}
