/**
 * Tool Execution Queue
 * 工具并发执行队列 — 参考 Claude Code 的 Q80 设计
 *
 * 核心逻辑：
 * - 并发安全的工具可以并行执行（如 search_knowledge + invoke_policy_expert）
 * - 非并发安全的工具必须串行执行（如 save_user_memory）
 * - 非安全工具会阻塞后续所有工具直到它完成
 */

import { Logger } from '@nestjs/common';

export interface QueuedTool {
  id: string;            // tool_use_id from Claude
  name: string;          // tool name
  input: Record<string, unknown>;
  isConcurrencySafe: boolean;
  status: 'queued' | 'executing' | 'completed' | 'failed';
  result?: string;
  error?: string;
  startedAt?: number;
  completedAt?: number;
  promise?: Promise<void>;
}

export type ToolExecutor = (
  toolName: string,
  toolInput: Record<string, unknown>,
  toolUseId: string,
) => Promise<{ output: string; isError: boolean }>;

export class ToolExecutionQueue {
  private readonly logger = new Logger(ToolExecutionQueue.name);
  private tools: QueuedTool[] = [];
  private executor: ToolExecutor;

  constructor(executor: ToolExecutor) {
    this.executor = executor;
  }

  /**
   * Add a tool to the execution queue
   */
  addTool(
    id: string,
    name: string,
    input: Record<string, unknown>,
    isConcurrencySafe: boolean,
  ): void {
    this.tools.push({
      id,
      name,
      input,
      isConcurrencySafe,
      status: 'queued',
    });
  }

  /**
   * Check if a tool can start executing now
   */
  private canExecute(tool: QueuedTool): boolean {
    const executing = this.tools.filter(t => t.status === 'executing');

    // If nothing is executing, always allow
    if (executing.length === 0) return true;

    // If the new tool is concurrency-safe AND all executing are safe → allow parallel
    if (tool.isConcurrencySafe && executing.every(t => t.isConcurrencySafe)) {
      return true;
    }

    // Otherwise block: non-safe tool must wait for queue to drain
    return false;
  }

  /**
   * Process the queue — start any tools that can run
   */
  private async processQueue(): Promise<void> {
    for (const tool of this.tools) {
      if (tool.status !== 'queued') continue;

      if (this.canExecute(tool)) {
        await this.executeTool(tool);
      } else if (!tool.isConcurrencySafe) {
        // Non-safe tool encountered — stop processing further
        // It will be picked up after current executing tools complete
        break;
      }
    }
  }

  /**
   * Execute a single tool
   */
  private async executeTool(tool: QueuedTool): Promise<void> {
    tool.status = 'executing';
    tool.startedAt = Date.now();

    this.logger.debug(`Executing tool: ${tool.name} (id: ${tool.id})`);

    const execution = (async () => {
      try {
        const result = await this.executor(tool.name, tool.input, tool.id);
        tool.result = result.output;
        tool.status = result.isError ? 'failed' : 'completed';
        if (result.isError) {
          tool.error = result.output;
        }
      } catch (error) {
        tool.status = 'failed';
        tool.error = error instanceof Error ? error.message : String(error);
        tool.result = `<tool_use_error>Error executing ${tool.name}: ${tool.error}</tool_use_error>`;
      } finally {
        tool.completedAt = Date.now();
        const duration = tool.completedAt - (tool.startedAt || tool.completedAt);
        this.logger.debug(
          `Tool completed: ${tool.name} (${tool.status}) in ${duration}ms`,
        );
      }
    })();

    tool.promise = execution;

    // After this tool completes, try to process more from the queue
    execution.finally(() => {
      this.processQueue();
    });
  }

  /**
   * Execute all queued tools and return results in order
   * This is the main entry point
   */
  async executeAll(
    toolUses: Array<{
      id: string;
      name: string;
      input: Record<string, unknown>;
    }>,
    concurrencyMap: Record<string, boolean>,
  ): Promise<Array<{ toolUseId: string; toolName: string; output: string; isError: boolean }>> {
    // Reset state
    this.tools = [];

    // Queue all tools
    for (const tu of toolUses) {
      const isSafe = concurrencyMap[tu.name] ?? true; // default to safe
      this.addTool(tu.id, tu.name, tu.input, isSafe);
    }

    // Start processing
    await this.processQueue();

    // Wait for all to complete
    await this.waitForAll();

    // Return results in original order
    return this.tools.map(t => ({
      toolUseId: t.id,
      toolName: t.name,
      output: t.result || `<tool_use_error>No result from ${t.name}</tool_use_error>`,
      isError: t.status === 'failed',
    }));
  }

  /**
   * Wait for all tools to finish executing
   */
  private async waitForAll(): Promise<void> {
    while (this.hasUnfinishedTools()) {
      const executing = this.tools
        .filter(t => t.status === 'executing' && t.promise)
        .map(t => t.promise!);

      if (executing.length > 0) {
        await Promise.race(executing);
      }

      // Process any newly unblocked tools
      await this.processQueue();
    }
  }

  /**
   * Check if there are still tools waiting or executing
   */
  private hasUnfinishedTools(): boolean {
    return this.tools.some(
      t => t.status === 'queued' || t.status === 'executing',
    );
  }

  /**
   * Get execution summary
   */
  getExecutionSummary(): {
    totalTools: number;
    parallelBatches: number;
    totalDurationMs: number;
    perTool: Array<{ name: string; durationMs: number; status: string }>;
  } {
    const minStart = Math.min(
      ...this.tools.map(t => t.startedAt || Infinity),
    );
    const maxEnd = Math.max(
      ...this.tools.map(t => t.completedAt || 0),
    );

    return {
      totalTools: this.tools.length,
      parallelBatches: this.countParallelBatches(),
      totalDurationMs: maxEnd > 0 ? maxEnd - minStart : 0,
      perTool: this.tools.map(t => ({
        name: t.name,
        durationMs: t.completedAt && t.startedAt
          ? t.completedAt - t.startedAt
          : 0,
        status: t.status,
      })),
    };
  }

  /**
   * Estimate how many parallel batches were executed
   */
  private countParallelBatches(): number {
    // Group tools by overlapping execution windows
    let batches = 0;
    let lastEndTime = 0;
    for (const tool of this.tools) {
      if (tool.startedAt && tool.startedAt >= lastEndTime) {
        batches++;
      }
      if (tool.completedAt) {
        lastEndTime = Math.max(lastEndTime, tool.completedAt);
      }
    }
    return batches || 1;
  }
}

// ============================================================
// Concurrency Map (工具并发安全性声明)
// ============================================================

/**
 * 定义每个工具是否可以并发执行
 * true = 可以并行（读操作）
 * false = 必须串行（写操作/副作用）
 */
export const TOOL_CONCURRENCY_MAP: Record<string, boolean> = {
  // Agent invocation tools — 互相独立，可并行
  invoke_policy_expert: true,
  invoke_assessment_expert: true,
  invoke_strategist: true,
  invoke_objection_handler: true,
  invoke_case_analyst: true,
  invoke_memory_manager: false, // 有写操作（save_user_memory）

  // Direct tools — 按操作类型
  search_knowledge: true,       // 读操作
  get_user_context: true,       // 读操作
  check_off_topic: true,        // 读操作
  get_current_datetime: true,   // 无副作用
  web_search: true,             // 读操作
  get_exchange_rate: true,      // 读操作
  fetch_immigration_news: true, // 读操作

  // 支付查询工具 — 只读
  check_payment_status: true,   // 读操作
  query_order_history: true,    // 读操作

  // 有副作用的工具 — 串行
  save_user_memory: false,      // 写操作
  generate_payment: false,      // 创建支付订单
  collect_assessment_info: false, // 写操作
  cancel_order: false,          // 取消订单
};
