/**
 * Agent Loop — 核心递归 async generator
 *
 * 借鉴 Claude Code 的 aM() 函数设计：
 * 1. 调 Claude API（流式）
 * 2. 收集响应
 * 3. 如果有 tool_use → 执行工具 → 递归
 * 4. 如果没有 tool_use → 结束
 *
 * 这是整个多 Agent 系统的心脏。
 */

import { Logger } from '@nestjs/common';
import Anthropic from '@anthropic-ai/sdk';
import {
  AgentLoopParams,
  ClaudeMessage,
  ContentBlock,
  SpecialistAgentType,
} from '../types/agent.types';
import {
  StreamEvent,
  AGENT_DISPLAY_NAMES,
  AGENT_DESCRIPTIONS,
} from '../types/stream.types';
import {
  ToolExecutionQueue,
  TOOL_CONCURRENCY_MAP,
} from '../tools/tool-execution-queue';
import {
  isAgentInvocationTool,
  getToolsForClaudeAPI,
} from '../tools/coordinator-tools';
import {
  INTENT_MAX_ANSWER_LENGTH,
  MAX_FOLLOWUP_LENGTH,
  smartTruncate,
} from '../schemas/coordinator-response.schema';

const logger = new Logger('AgentLoop');

// ============================================================
// Cost Estimation
// ============================================================

/** 估算 token 成本 (USD) — Sonnet 4 pricing */
function estimateCost(inputTokens: number, outputTokens: number): number {
  const inputCostPer1M = 3.0;   // $3/1M input tokens
  const outputCostPer1M = 15.0; // $15/1M output tokens
  return (
    (inputTokens / 1_000_000) * inputCostPer1M +
    (outputTokens / 1_000_000) * outputCostPer1M
  );
}

// ============================================================
// Tool Executor Factory
// ============================================================

/**
 * 工具执行器类型
 * Coordinator 在初始化时注入实际的执行函数
 */
export type CoordinatorToolExecutor = (
  toolName: string,
  toolInput: Record<string, unknown>,
  toolUseId: string,
) => Promise<{ output: string; isError: boolean }>;

// ============================================================
// Agent Loop
// ============================================================

/**
 * 核心递归 async generator
 *
 * 每次递归（turn）：
 * 1. 调用 Claude API 流式输出
 * 2. 收集 text blocks 和 tool_use blocks
 * 3. 如果有 tool_use → 并发执行 → 递归
 * 4. 如果没有 tool_use → yield end event → 结束
 */
export async function* agentLoop(
  anthropicClient: Anthropic,
  params: AgentLoopParams,
  toolExecutor: CoordinatorToolExecutor,
  additionalTools?: Array<{ name: string; description: string; input_schema: Record<string, unknown> }>,
  additionalConcurrencyMap?: Record<string, boolean>,
): AsyncGenerator<StreamEvent> {
  const {
    messages,
    systemPrompt,
    maxTurns,
    maxBudgetUsd,
    conversationId,
    abortSignal,
  } = params;

  const currentTurn = params.currentTurnCount || 0;
  const currentCost = params.currentCostUsd || 0;
  let totalInputTokens = 0;
  let totalOutputTokens = 0;
  const agentsUsed: SpecialistAgentType[] = [];

  // ---- Safety Checks ----
  if (currentTurn >= maxTurns) {
    yield {
      type: 'error',
      code: 'MAX_TURNS_REACHED',
      message: `已达到最大循环次数 (${maxTurns})，自动停止。`,
      recoverable: true,
    };
    return;
  }

  if (currentCost >= maxBudgetUsd) {
    yield {
      type: 'error',
      code: 'BUDGET_EXCEEDED',
      message: `已超出成本预算 ($${maxBudgetUsd.toFixed(2)})，自动停止。`,
      recoverable: true,
    };
    return;
  }

  if (abortSignal?.aborted) {
    yield {
      type: 'error',
      code: 'USER_ABORTED',
      message: '用户取消了请求。',
      recoverable: false,
    };
    return;
  }

  // ---- Emit Coordinator Thinking ----
  if (currentTurn > 0) {
    yield {
      type: 'coordinator_thinking',
      phase: 'synthesizing',
      message: '正在综合分析结果...',
      timestamp: Date.now(),
    };
  }

  // ---- Call Claude API (Streaming, with retry for rate limits) ----
  logger.debug(
    `[Turn ${currentTurn + 1}/${maxTurns}] Calling Claude API with ${messages.length} messages`,
  );

  let stream!: ReturnType<typeof anthropicClient.messages.stream>;
  const MAX_RETRIES = 2;
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
    try {
      stream = anthropicClient.messages.stream({
        model: 'claude-sonnet-4-5-20250929',
        system: systemPrompt,
        messages: messages as any,
        tools: getToolsForClaudeAPI(additionalTools) as any,
        max_tokens: 2048,
        ...(params.outputConfig ? { output_config: params.outputConfig } : {}),
      } as any);
      break; // success
    } catch (error: any) {
      const isRateLimit = error?.status === 429 || error?.error?.type === 'rate_limit_error';
      const isOverloaded = error?.status === 529 || error?.error?.type === 'overloaded_error';

      if ((isRateLimit || isOverloaded) && attempt < MAX_RETRIES) {
        const retryAfter = isRateLimit ? Math.pow(2, attempt + 1) * 1000 : 3000;
        logger.warn(`API ${isRateLimit ? 'rate limited' : 'overloaded'}, retrying in ${retryAfter}ms (attempt ${attempt + 1}/${MAX_RETRIES})`);
        yield {
          type: 'coordinator_thinking',
          phase: 'analyzing',
          message: '系统繁忙，正在重试...',
          timestamp: Date.now(),
        };
        await new Promise(resolve => setTimeout(resolve, retryAfter));
        continue;
      }

      const errMsg = error instanceof Error ? error.message : String(error);
      logger.error(`Claude API stream creation failed: ${errMsg}`);
      yield {
        type: 'error',
        code: isRateLimit ? 'RATE_LIMIT' : isOverloaded ? 'OVERLOADED' : 'API_ERROR',
        message: `API 调用失败: ${errMsg}`,
        recoverable: isRateLimit || isOverloaded,
      };
      return;
    }
  }

  // ---- Collect Response Blocks ----
  const assistantBlocks: ContentBlock[] = [];
  let currentTextContent = '';

  try {
    for await (const event of stream) {
      // Check abort
      if (abortSignal?.aborted) {
        yield {
          type: 'error',
          code: 'USER_ABORTED',
          message: '用户取消了请求。',
          recoverable: false,
        };
        return;
      }

      // Process stream events
      if (event.type === 'content_block_delta') {
        const delta = (event as any).delta;

        if (delta.type === 'text_delta') {
          currentTextContent += delta.text;
          // Structured Output 模式下不直接 yield text（JSON 片段不能展示给用户）
          if (!params.outputConfig) {
            yield {
              type: 'text',
              content: delta.text,
              timestamp: Date.now(),
            };
          }
        } else if (delta.type === 'input_json_delta') {
          // Tool input being streamed — accumulate silently
        }
      } else if (event.type === 'content_block_start') {
        const block = (event as any).content_block;

        if (block.type === 'text') {
          currentTextContent = '';
        } else if (block.type === 'tool_use') {
          // Emit tool_use event for frontend
          yield {
            type: 'tool_use',
            toolName: block.name,
            toolInput: {},
            toolUseId: block.id,
            timestamp: Date.now(),
          };

          // If it's an Agent invocation, emit agent_start
          if (isAgentInvocationTool(block.name)) {
            const agentType = toolNameToAgentType(block.name);
            if (agentType) {
              agentsUsed.push(agentType);
              yield {
                type: 'agent_start',
                agentType,
                agentName: AGENT_DISPLAY_NAMES[agentType],
                description: AGENT_DESCRIPTIONS[agentType],
                timestamp: Date.now(),
              };
            }
          }
        }
      } else if (event.type === 'content_block_stop') {
        // Block completed — add to assistantBlocks
        if (currentTextContent) {
          assistantBlocks.push({ type: 'text', text: currentTextContent });
          currentTextContent = '';
        }
      } else if (event.type === 'message_delta') {
        // Message-level info (stop_reason, usage)
      }
    }
  } catch (error) {
    const errMsg = error instanceof Error ? error.message : String(error);
    logger.error(`Stream processing error: ${errMsg}`);
    yield {
      type: 'error',
      code: 'API_ERROR',
      message: `流式处理错误: ${errMsg}`,
      recoverable: true,
    };
    return;
  }

  // ---- Get Final Message ----
  let finalMessage: Anthropic.Message;
  try {
    finalMessage = await stream.finalMessage();
  } catch (error) {
    const errMsg = error instanceof Error ? error.message : String(error);
    logger.error(`Failed to get final message: ${errMsg}`);
    yield {
      type: 'error',
      code: 'API_ERROR',
      message: `获取响应失败: ${errMsg}`,
      recoverable: true,
    };
    return;
  }

  // ---- Track Tokens ----
  const turnInputTokens = finalMessage.usage.input_tokens;
  const turnOutputTokens = finalMessage.usage.output_tokens;
  totalInputTokens += turnInputTokens;
  totalOutputTokens += turnOutputTokens;
  const turnCost = estimateCost(turnInputTokens, turnOutputTokens);

  yield {
    type: 'usage',
    usage: {
      inputTokens: turnInputTokens,
      outputTokens: turnOutputTokens,
      cacheCreationTokens: (finalMessage.usage as any).cache_creation_input_tokens || 0,
      cacheReadTokens: (finalMessage.usage as any).cache_read_input_tokens || 0,
    },
    costUsd: turnCost,
    timestamp: Date.now(),
  };

  logger.debug(
    `[Turn ${currentTurn + 1}] Tokens: ${turnInputTokens}in/${turnOutputTokens}out, Cost: $${turnCost.toFixed(4)}`,
  );

  // ---- Extract Tool Uses ----
  const toolUseBlocks = finalMessage.content.filter(
    (b): b is Anthropic.ToolUseBlock => b.type === 'tool_use',
  );

  // If no tool_use → conversation is done (with optional evaluation gate)
  if (toolUseBlocks.length === 0 || finalMessage.stop_reason === 'end_turn') {
    // ---- Extract response text from finalMessage (修复 bug：currentTextContent 在 content_block_stop 时已被重置为空) ----
    const responseText = finalMessage.content
      .filter((b): b is Anthropic.TextBlock => b.type === 'text')
      .map(b => b.text)
      .join('');

    // ---- Structured Output 解析：从 JSON 中提取 answer，强制截断，yield ----
    if (params.outputConfig && responseText) {
      try {
        const parsed = JSON.parse(responseText);
        if (parsed.answer) {
          // 按 intent 强制截断 answer
          const maxLen = INTENT_MAX_ANSWER_LENGTH[parsed.intent] || 300;
          const originalLen = parsed.answer.length;
          const answer = smartTruncate(parsed.answer, maxLen);

          if (originalLen > maxLen) {
            logger.debug(
              `[Turn ${currentTurn + 1}] Answer truncated: ${originalLen} → ${answer.length} chars (intent=${parsed.intent}, limit=${maxLen})`,
            );
          }

          yield { type: 'text', content: answer, timestamp: Date.now() };

          // followUp 必须是面向用户的问题（含？），过滤掉内部策略备注
          if (parsed.followUp && /？|\?/.test(parsed.followUp)) {
            const followUp = smartTruncate(parsed.followUp, MAX_FOLLOWUP_LENGTH);
            yield { type: 'text', content: '\n\n' + followUp, timestamp: Date.now() };
          }
        } else {
          // JSON 合法但缺少 answer 字段 → yield 原始文本
          yield { type: 'text', content: responseText, timestamp: Date.now() };
        }
        logger.debug(`[Turn ${currentTurn + 1}] Structured output intent: ${parsed.intent}`);
      } catch {
        // JSON 解析失败 → 回退到原始文本
        logger.warn(`[Turn ${currentTurn + 1}] Structured output parse failed, falling back to raw text`);
        yield { type: 'text', content: responseText, timestamp: Date.now() };
      }
    }

    // --- Evaluation Gate (optional, zero-config safe) ---
    if (params.evaluationGate) {
      try {
        const gateResult = await params.evaluationGate(
          responseText,
          currentTurn + 1,
          agentsUsed,
        );

        if (
          !gateResult.passed &&
          (gateResult.action === 'RETRY' || gateResult.action === 'SUPPLEMENT')
        ) {
          // Safety: don't retry if near limits
          if (currentTurn + 1 < maxTurns && currentCost + turnCost < maxBudgetUsd) {
            logger.debug(
              `[Turn ${currentTurn + 1}] Evaluation gate: ${gateResult.action}, retrying...`,
            );

            yield {
              type: 'coordinator_thinking',
              phase: 'evaluating',
              message: '质量检查未通过，正在重新生成...',
              timestamp: Date.now(),
            };

            const feedbackMessage: ClaudeMessage = {
              role: 'user',
              content: gateResult.feedbackForModel || '[系统] 请重新生成回复。',
            };

            const retryMessages: ClaudeMessage[] = [
              ...messages,
              { role: 'assistant', content: finalMessage.content as ContentBlock[] },
              feedbackMessage,
            ];

            // Recurse WITHOUT evaluationGate to prevent infinite loops (max 1 retry)
            yield* agentLoop(
              anthropicClient,
              {
                ...params,
                messages: retryMessages,
                currentTurnCount: currentTurn + 1,
                currentCostUsd: currentCost + turnCost,
                evaluationGate: undefined,
              },
              toolExecutor,
              additionalTools,
              additionalConcurrencyMap,
            );
            return;
          }
        }

        // WARN_AND_PASS or ESCALATE: emit warning event and continue to 'end'
        if (!gateResult.passed) {
          yield {
            type: 'evaluation_warning',
            results: gateResult.results.filter(r => !r.passed),
            action: gateResult.action,
            timestamp: Date.now(),
          };
        }
      } catch (gateError) {
        // Gate error is non-fatal — log and continue
        logger.error(`Evaluation gate error: ${gateError}`);
      }
    }
    // --- End Evaluation Gate ---

    yield {
      type: 'end',
      totalTokens: {
        inputTokens: totalInputTokens,
        outputTokens: totalOutputTokens,
      },
      totalCostUsd: currentCost + turnCost,
      turnCount: currentTurn + 1,
      agentsUsed,
      timestamp: Date.now(),
    };
    return;
  }

  // ---- Execute Tools (Concurrent Queue) ----
  yield {
    type: 'coordinator_thinking',
    phase: 'orchestrating',
    message: `正在执行 ${toolUseBlocks.length} 个工具调用...`,
    timestamp: Date.now(),
  };

  const queue = new ToolExecutionQueue(toolExecutor);
  const mergedConcurrencyMap = additionalConcurrencyMap
    ? { ...TOOL_CONCURRENCY_MAP, ...additionalConcurrencyMap }
    : TOOL_CONCURRENCY_MAP;
  const toolResults = await queue.executeAll(
    toolUseBlocks.map(tb => ({
      id: tb.id,
      name: tb.name,
      input: tb.input as Record<string, unknown>,
    })),
    mergedConcurrencyMap,
  );

  // Log execution summary
  const summary = queue.getExecutionSummary();
  logger.debug(
    `[Turn ${currentTurn + 1}] Tools executed: ${summary.totalTools} tools in ${summary.parallelBatches} batch(es), ${summary.totalDurationMs}ms total`,
  );

  // ---- Emit Tool Results & Agent Completions ----
  for (const result of toolResults) {
    // Emit tool_result event
    yield {
      type: 'tool_result',
      toolName: result.toolName,
      toolUseId: result.toolUseId,
      result: result.output,
      isError: result.isError,
      timestamp: Date.now(),
    };

    // If it's an agent completion, emit agent_complete
    if (isAgentInvocationTool(result.toolName)) {
      const agentType = toolNameToAgentType(result.toolName);
      if (agentType) {
        const toolSummary = summary.perTool.find(t => t.name === result.toolName);
        yield {
          type: 'agent_complete',
          agentType,
          agentName: AGENT_DISPLAY_NAMES[agentType],
          durationMs: toolSummary?.durationMs || 0,
          success: !result.isError,
          timestamp: Date.now(),
        };
      }
    }
  }

  // ---- Build Messages for Recursion ----
  const toolResultMessages: ContentBlock[] = toolResults.map(r => ({
    type: 'tool_result' as const,
    tool_use_id: r.toolUseId,
    content: r.output,
    is_error: r.isError,
  }));

  const nextMessages: ClaudeMessage[] = [
    ...messages,
    { role: 'assistant', content: finalMessage.content as ContentBlock[] },
    { role: 'user', content: toolResultMessages },
  ];

  // ---- Recurse ----
  yield* agentLoop(
    anthropicClient,
    {
      ...params,
      messages: nextMessages,
      currentTurnCount: currentTurn + 1,
      currentCostUsd: currentCost + turnCost,
    },
    toolExecutor,
    additionalTools,
    additionalConcurrencyMap,
  );
}

// ============================================================
// Helpers
// ============================================================

/** Map tool name to agent type */
function toolNameToAgentType(toolName: string): SpecialistAgentType | null {
  const mapping: Record<string, SpecialistAgentType> = {
    invoke_policy_expert: SpecialistAgentType.POLICY_EXPERT,
    invoke_assessment_expert: SpecialistAgentType.ASSESSMENT_EXPERT,
    invoke_strategist: SpecialistAgentType.STRATEGIST,
    invoke_objection_handler: SpecialistAgentType.OBJECTION_HANDLER,
    invoke_case_analyst: SpecialistAgentType.CASE_ANALYST,
    invoke_memory_manager: SpecialistAgentType.MEMORY_MANAGER,
  };
  return mapping[toolName] || null;
}
