/**
 * Coordinator Agent Service
 * 主协调器服务 — 替代 ClaudeAgentServiceV2
 *
 * 核心职责：
 * 1. 接收用户消息，构建 Claude API 调用上下文
 * 2. 启动 agent-loop 递归循环
 * 3. 将工具调用分派给对应的专家 Agent 或直接执行
 * 4. 产出流式事件给 ConversationService
 */

import { Injectable, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Anthropic from '@anthropic-ai/sdk';
import { Logger } from '@nestjs/common';

import { agentLoop, CoordinatorToolExecutor } from './agent-loop';
import { ContextInjectorService } from './context-injector.service';
import {
  buildCoordinatorSystemPrompt,
  CoordinatorPromptConfig,
} from '../prompts/coordinator-system-prompt';

// Structured Output
import { zodOutputFormat } from '@anthropic-ai/sdk/helpers/zod';
import { CoordinatorResponseSchema } from '../schemas/coordinator-response.schema';

// Specialist Services
import { PolicyExpertService } from '../specialists/policy-expert.service';
import { AssessmentExpertService } from '../specialists/assessment-expert.service';
import { StrategistService } from '../specialists/strategist.service';
import { ObjectionHandlerService } from '../specialists/objection-handler.service';
import { CaseAnalystService } from '../specialists/case-analyst.service';
import { MemoryManagerService } from '../specialists/memory-manager.service';

// Types
import {
  AgentLoopParams,
  ClaudeMessage,
  SystemPromptBlock,
} from '../types/agent.types';
import { StreamEvent } from '../types/stream.types';
import { ConversationContext } from '../types/context.types';
import { isAgentInvocationTool } from '../tools/coordinator-tools';

// External services
import { KnowledgeClientService } from '../../knowledge/knowledge-client.service';
import { TokenUsageService } from '../../claude/token-usage.service';
import { ImmigrationToolsService } from '../../claude/tools/immigration-tools.service';
import { ConversationContext as OldConversationContext } from '../../claude/claude-agent.service';

// MCP Integration
import { McpClientService } from '../mcp/mcp-client.service';

// Evaluation Gate
import { EvaluationGateService } from './evaluation-gate.service';

// Input Gate
import { InputGateService } from './input-gate.service';

// ============================================================
// Compatibility Types (与 ClaudeAgentServiceV2 的 StreamChunk 兼容)
// ============================================================

export interface FileAttachment {
  id: string;
  originalName: string;
  mimeType: string;
  type: 'image' | 'document' | 'audio' | 'video' | 'other';
  size: number;
  downloadUrl?: string;
  thumbnailUrl?: string;
}

/** 兼容旧版 StreamChunk 格式 */
export interface StreamChunk {
  type: 'text' | 'tool_use' | 'tool_result' | 'end' | 'usage' | 'stage_change' | 'state_update' | 'error'
    | 'agent_start' | 'agent_progress' | 'agent_complete' | 'coordinator_thinking' | 'evaluation_warning';
  content?: string;
  toolName?: string;
  toolInput?: Record<string, unknown>;
  toolResult?: unknown;
  stageName?: string;
  newState?: any;
  inputTokens?: number;
  outputTokens?: number;
  errorMessage?: string;
  // Multi-agent fields
  agentType?: string;
  agentName?: string;
  description?: string;
  message?: string;
  phase?: string;
  durationMs?: number;
  success?: boolean;
  // Evaluation gate fields
  results?: Array<{ ruleId: string; ruleName: string; ruleType: string; passed: boolean; failureAction: string; message?: string }>;
  action?: string;
}

/** 兼容旧版 ConversationContext */
export interface LegacyConversationContext {
  userId: string;
  conversationId: string;
  userMemory?: string[];
  previousMessages?: Array<{
    role: 'user' | 'assistant';
    content: string;
    attachments?: FileAttachment[];
  }>;
  consultingState?: any;
  deviceInfo?: {
    ip?: string;
    userAgent?: string;
    fingerprint?: string;
  };
}

// ============================================================
// Coordinator Agent Service
// ============================================================

@Injectable()
export class CoordinatorAgentService implements OnModuleInit {
  private readonly logger = new Logger(CoordinatorAgentService.name);
  private anthropicClient: Anthropic;
  private coordinatorSystemPrompt: string;

  /** 默认配置 */
  private readonly MAX_TURNS = 15;
  private readonly MAX_BUDGET_USD = 0.50; // 单次对话最大成本

  constructor(
    private readonly configService: ConfigService,
    private readonly knowledgeClient: KnowledgeClientService,
    private readonly tokenUsageService: TokenUsageService,
    private readonly contextInjector: ContextInjectorService,
    // Legacy tool service (production-tested implementations)
    private readonly immigrationTools: ImmigrationToolsService,
    // Specialist agents
    private readonly policyExpert: PolicyExpertService,
    private readonly assessmentExpert: AssessmentExpertService,
    private readonly strategist: StrategistService,
    private readonly objectionHandler: ObjectionHandlerService,
    private readonly caseAnalyst: CaseAnalystService,
    private readonly memoryManager: MemoryManagerService,
    // MCP client
    private readonly mcpClient: McpClientService,
    // Evaluation gate
    private readonly evaluationGate: EvaluationGateService,
    // Input gate
    private readonly inputGate: InputGateService,
  ) {}

  onModuleInit() {
    // Initialize Anthropic client
    const baseUrl = this.configService.get<string>('ANTHROPIC_BASE_URL');
    const isProxyUrl =
      baseUrl &&
      (baseUrl.includes('67.223.119.33') || baseUrl.match(/^\d+\.\d+\.\d+\.\d+/));

    if (isProxyUrl) {
      this.logger.log(`Using Anthropic proxy: ${baseUrl}`);
      process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
    }

    this.anthropicClient = new Anthropic({
      apiKey: this.configService.get<string>('ANTHROPIC_API_KEY'),
      baseURL: baseUrl || undefined,
    });

    // Build system prompt
    const promptConfig: CoordinatorPromptConfig = {
      companyName: this.configService.get<string>('COMPANY_NAME') || '互信咨询',
      companyDescription:
        this.configService.get<string>('COMPANY_DESCRIPTION') ||
        '专业香港移民咨询服务商',
      supportedCategories: ['QMAS', 'GEP', 'IANG', 'TTPS', 'CIES', 'TECHTAS'],
    };
    this.coordinatorSystemPrompt = buildCoordinatorSystemPrompt(promptConfig);

    this.logger.log(
      `CoordinatorAgentService initialized (maxTurns=${this.MAX_TURNS}, maxBudget=$${this.MAX_BUDGET_USD})`,
    );
  }

  /**
   * Send message — 主入口
   * 兼容 ClaudeAgentServiceV2 的接口
   */
  async *sendMessage(
    userContent: string,
    context: LegacyConversationContext,
    attachments?: FileAttachment[],
  ): AsyncGenerator<StreamChunk> {
    const startTime = Date.now();

    try {
      // 0. Input Gate — 轻量级预检
      const gateResult = await this.inputGate.classify(userContent);
      if (gateResult.classification !== 'ON_TOPIC' && gateResult.fixedResponse) {
        yield { type: 'text', content: gateResult.fixedResponse };
        yield { type: 'end', inputTokens: 0, outputTokens: 0 };
        return;
      }

      // 1. Build messages from conversation history
      const messages = this.buildMessages(context, userContent, attachments);

      // 2. Build system prompt blocks (with cache control)
      const systemPrompt = this.buildSystemPromptBlocks();

      // 3. Inject dynamic context
      const enrichedMessages = await this.contextInjector.inject(
        {
          conversationId: context.conversationId,
          userId: context.userId,
          messages: (context.previousMessages || []).map((m) => ({
            role: m.role as 'user' | 'assistant',
            content: m.content,
            createdAt: new Date(),
          })),
          consultingState: context.consultingState,
          deviceInfo: context.deviceInfo,
          isNewConversation: !context.previousMessages?.length,
        },
        messages,
      );

      // 4. Create abort controller
      const abortController = new AbortController();

      // 5. Build evaluation gate callback
      const evaluationGateCallback = async (
        responseText: string,
        turnCount: number,
        agentsUsedInLoop: string[],
      ) => {
        const stage = context.consultingState?.currentStageId || null;
        const gateResult = await this.evaluationGate.evaluate(null, {
          stage,
          collectedInfo: context.consultingState?.collectedInfo || null,
          assessmentResult: context.consultingState?.assessmentResult || null,
          responseText,
          turnCount,
          messageCount: context.previousMessages?.length || 0,
          hasConverted: false,
          agentsUsed: agentsUsedInLoop,
          userMessage: userContent,
        });

        // Gate 失败时，将失败教训异步保存为系统经验（fire-and-forget）
        if (
          !gateResult.passed &&
          (gateResult.action === 'RETRY' || gateResult.action === 'SUPPLEMENT')
        ) {
          this.persistGateFailureAsExperience(
            gateResult,
            stage,
            context.conversationId,
          ).catch(err =>
            this.logger.warn(`Failed to persist gate failure experience: ${err}`),
          );
        }

        return gateResult;
      };

      // 6. Build agent loop params
      const loopParams: AgentLoopParams = {
        messages: enrichedMessages,
        systemPrompt,
        tools: [], // Tools are set in agent-loop via getToolsForClaudeAPI()
        maxTurns: this.MAX_TURNS,
        maxBudgetUsd: this.MAX_BUDGET_USD,
        conversationId: context.conversationId,
        userId: context.userId,
        abortSignal: abortController.signal,
        currentTurnCount: 0,
        currentCostUsd: 0,
        evaluationGate: evaluationGateCallback,
        outputConfig: { format: zodOutputFormat(CoordinatorResponseSchema) as any },
      };

      // 6. Create tool executor
      const toolExecutor = this.createToolExecutor(context.userId, context.conversationId);

      // 6.5. Gather MCP tools (if any MCP servers are connected)
      const mcpDiscoveredTools = this.mcpClient.getDiscoveredTools();
      const mcpTools = mcpDiscoveredTools.length > 0
        ? mcpDiscoveredTools.map(t => ({
            name: t.name,
            description: t.description,
            input_schema: t.input_schema,
          }))
        : undefined;
      const mcpConcurrencyMap = mcpDiscoveredTools.length > 0
        ? this.mcpClient.getMcpConcurrencyMap()
        : undefined;

      // 7. Run agent loop and map events to StreamChunk
      for await (const event of agentLoop(
        this.anthropicClient,
        loopParams,
        toolExecutor,
        mcpTools,
        mcpConcurrencyMap,
      )) {
        yield this.mapEventToStreamChunk(event);
      }
    } catch (error) {
      const errMsg = error instanceof Error ? error.message : String(error);
      this.logger.error(`sendMessage error: ${errMsg}`);
      yield {
        type: 'error',
        errorMessage: errMsg,
        content: `抱歉，处理消息时出错：${errMsg}`,
      };
    }

    const duration = Date.now() - startTime;
    this.logger.log(
      `[${context.conversationId}] Message processed in ${duration}ms`,
    );
  }

  // ============================================================
  // Message Building
  // ============================================================

  /**
   * 将附件转换为 Claude API content blocks
   * - image → { type: 'image', source: { type: 'url', url } }
   * - PDF   → { type: 'document', source: { type: 'url', url }, title }
   * - text/csv/json/md → 嵌入为带文件名提示的 text block
   */
  private buildAttachmentBlocks(attachments: FileAttachment[]): any[] {
    const blocks: any[] = [];

    for (const att of attachments) {
      if (!att.downloadUrl) continue;

      if (att.type === 'image') {
        blocks.push({
          type: 'image',
          source: { type: 'url', url: att.downloadUrl },
        });
      } else if (att.mimeType === 'application/pdf') {
        // Claude 原生支持 PDF 文档
        blocks.push({
          type: 'document',
          source: { type: 'url', url: att.downloadUrl },
          title: att.originalName,
        });
      } else if (this.isTextBasedMime(att.mimeType)) {
        // 文本类文件：提示模型通过 URL 获取内容
        blocks.push({
          type: 'text',
          text: `[附件: ${att.originalName} (${att.mimeType})] — 文件内容可通过URL获取: ${att.downloadUrl}`,
        });
      }
    }

    return blocks;
  }

  /** 判断是否为可直接阅读的文本类 MIME */
  private isTextBasedMime(mimeType: string): boolean {
    const textMimes = [
      'text/plain', 'text/csv', 'text/markdown',
      'application/json',
    ];
    return textMimes.includes(mimeType) || mimeType.startsWith('text/');
  }

  private buildMessages(
    context: LegacyConversationContext,
    userContent: string,
    attachments?: FileAttachment[],
  ): ClaudeMessage[] {
    const messages: ClaudeMessage[] = [];

    // Convert previous messages
    if (context.previousMessages) {
      for (const msg of context.previousMessages) {
        if (msg.attachments?.length) {
          const contentBlocks = this.buildAttachmentBlocks(msg.attachments);
          contentBlocks.push({ type: 'text', text: msg.content });
          messages.push({ role: msg.role, content: contentBlocks });
        } else {
          messages.push({ role: msg.role, content: msg.content });
        }
      }
    }

    // Build current user message
    if (attachments?.length) {
      const contentBlocks = this.buildAttachmentBlocks(attachments);
      contentBlocks.push({ type: 'text', text: userContent });
      messages.push({ role: 'user', content: contentBlocks });
    } else {
      messages.push({ role: 'user', content: userContent });
    }

    return messages;
  }

  private buildSystemPromptBlocks(): SystemPromptBlock[] {
    return [
      {
        type: 'text',
        text: this.coordinatorSystemPrompt,
        cache_control: { type: 'ephemeral' },
      },
    ];
  }

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

  /**
   * 创建工具执行器 — 将工具调用分派给专家 Agent 或直接执行
   */
  private createToolExecutor(userId: string, conversationId: string): CoordinatorToolExecutor {
    return async (
      toolName: string,
      toolInput: Record<string, unknown>,
      toolUseId: string,
    ): Promise<{ output: string; isError: boolean }> => {
      try {
        if (isAgentInvocationTool(toolName)) {
          return await this.executeAgentTool(toolName, toolInput);
        } else if (this.mcpClient.isMcpTool(toolName)) {
          return await this.mcpClient.executeTool(toolName, toolInput);
        } else {
          return await this.executeDirectTool(toolName, toolInput, userId, conversationId);
        }
      } catch (error) {
        const errMsg = error instanceof Error ? error.message : String(error);
        this.logger.error(`Tool execution error [${toolName}]: ${errMsg}`);
        return {
          output: `<tool_use_error>工具 ${toolName} 执行失败: ${errMsg}</tool_use_error>`,
          isError: true,
        };
      }
    };
  }

  /**
   * 执行 Agent 调用工具 — 调用对应的专家 Agent
   */
  private async executeAgentTool(
    toolName: string,
    toolInput: Record<string, unknown>,
  ): Promise<{ output: string; isError: boolean }> {
    switch (toolName) {
      case 'invoke_policy_expert': {
        const result = await this.policyExpert.executeQuery({
          query: toolInput.query as string,
          category: toolInput.category as string | undefined,
          includeProcessSteps: toolInput.includeProcessSteps as boolean | undefined,
          includeRequirements: toolInput.includeRequirements as boolean | undefined,
        });
        return { output: result, isError: false };
      }

      case 'invoke_assessment_expert': {
        const result = await this.assessmentExpert.executeAssessment({
          userInfo: (toolInput.userInfo as Record<string, unknown>) || {},
          targetCategories: toolInput.targetCategories as string[] | undefined,
          conversationContext: toolInput.conversationContext as string | undefined,
        });
        return { output: result, isError: false };
      }

      case 'invoke_strategist': {
        const result = await this.strategist.getStrategy({
          conversationSummary: (toolInput.conversationSummary as string) || '',
          currentTurnCount: (toolInput.currentTurnCount as number) || 0,
          collectedInfo: (toolInput.collectedInfo as Record<string, unknown>) || {},
          userSentiment: toolInput.userSentiment as string | undefined,
          hasAssessment: (toolInput.hasAssessment as boolean) || false,
          hasConverted: (toolInput.hasConverted as boolean) || false,
        });
        return { output: result, isError: false };
      }

      case 'invoke_objection_handler': {
        const result = await this.objectionHandler.handleObjection({
          objection: (toolInput.objection as string) || '',
          userContext: (toolInput.userContext as string) || '',
          previousObjections: toolInput.previousObjections as string[] | undefined,
        });
        return { output: result, isError: false };
      }

      case 'invoke_case_analyst': {
        const result = await this.caseAnalyst.analyzeCases({
          userProfile: (toolInput.userProfile as Record<string, unknown>) || {},
          targetCategory: (toolInput.targetCategory as string) || '',
          focusArea: toolInput.focusArea as string | undefined,
        });
        return { output: result, isError: false };
      }

      case 'invoke_memory_manager': {
        const result = await this.memoryManager.manageMemory({
          action: (toolInput.action as any) || 'load_context',
          userId: (toolInput.userId as string) || '',
          dataToSave: toolInput.dataToSave as Record<string, unknown> | undefined,
          recentMessages: toolInput.recentMessages as string | undefined,
          contextQuery: toolInput.contextQuery as string | undefined,
        });
        return { output: result, isError: false };
      }

      default:
        return {
          output: `Unknown agent tool: ${toolName}`,
          isError: true,
        };
    }
  }

  /**
   * 执行直接工具 — 委托给经过生产测试的 ImmigrationToolsService
   */
  private async executeDirectTool(
    toolName: string,
    toolInput: Record<string, unknown>,
    userId: string,
    conversationId: string,
  ): Promise<{ output: string; isError: boolean }> {
    // 构建旧版 ConversationContext（ImmigrationToolsService 需要）
    const legacyContext: OldConversationContext = {
      userId,
      conversationId,
    };

    try {
      const result = await this.immigrationTools.executeTool(
        toolName,
        toolInput,
        legacyContext,
      );

      // ImmigrationToolsService 返回结构化对象，序列化为 string 供 Claude 消费
      const output = typeof result === 'string' ? result : JSON.stringify(result);
      const isError = typeof result === 'object' && result !== null && (result as any).error;

      return { output, isError: !!isError };
    } catch (error) {
      const errMsg = error instanceof Error ? error.message : String(error);
      this.logger.error(`Direct tool [${toolName}] failed: ${errMsg}`);
      return {
        output: `工具 ${toolName} 执行失败: ${errMsg}`,
        isError: true,
      };
    }
  }

  // ============================================================
  // Gate Failure → System Experience
  // ============================================================

  /**
   * 将评估门控失败沉淀为系统经验
   * 经验写入 knowledge-service，状态为 PENDING，需 admin 审核后激活
   * 激活后，未来对话中 Context Injector 的 searchExperiences() 会检索到并注入 prompt
   * 从而让 AI 在第一次生成回复时就遵循人工经验，避免重复犯错
   */
  private async persistGateFailureAsExperience(
    gateResult: import('./evaluation-gate.service').GateResult,
    stage: string | null,
    conversationId: string,
  ): Promise<void> {
    const failedRules = gateResult.results.filter(r => !r.passed);
    if (failedRules.length === 0) return;

    const stageLabel = stage || '通用';

    for (const rule of failedRules) {
      const scenario = `${stageLabel}阶段 — ${rule.ruleName}`;
      const content = `在${stageLabel}阶段，${rule.message || rule.ruleName + '检查未通过'}。AI 回复前应确保满足此要求，避免生成不合格内容后重试。`;

      await this.knowledgeClient.saveExperience({
        experienceType: 'KNOWLEDGE_GAP',
        scenario,
        content,
        sourceConversationId: conversationId,
        confidence: 45,
      });

      this.logger.debug(
        `Persisted gate failure as experience: [${rule.ruleType}] ${scenario}`,
      );
    }
  }

  // ============================================================
  // Event Mapping
  // ============================================================

  /**
   * 将 StreamEvent 映射为兼容旧版的 StreamChunk
   */
  private mapEventToStreamChunk(event: StreamEvent): StreamChunk {
    switch (event.type) {
      case 'text':
        return { type: 'text', content: event.content };

      case 'tool_use':
        return {
          type: 'tool_use',
          toolName: event.toolName,
          toolInput: event.toolInput,
        };

      case 'tool_result':
        return {
          type: 'tool_result',
          toolName: event.toolName,
          toolResult: event.result,
        };

      case 'agent_start':
        return {
          type: 'agent_start',
          agentType: event.agentType,
          agentName: event.agentName,
          description: event.description,
        };

      case 'agent_progress':
        return {
          type: 'agent_progress',
          agentType: event.agentType,
          message: event.message,
        };

      case 'agent_complete':
        return {
          type: 'agent_complete',
          agentType: event.agentType,
          agentName: event.agentName,
          durationMs: event.durationMs,
          success: event.success,
        };

      case 'coordinator_thinking':
        return {
          type: 'coordinator_thinking',
          phase: event.phase,
          message: event.message,
        };

      case 'usage':
        return {
          type: 'usage',
          inputTokens: event.usage.inputTokens,
          outputTokens: event.usage.outputTokens,
        };

      case 'end':
        return {
          type: 'end',
          inputTokens: event.totalTokens.inputTokens,
          outputTokens: event.totalTokens.outputTokens,
        };

      case 'error':
        return {
          type: 'error',
          errorMessage: event.message,
          content: event.message,
        };

      case 'state_update':
        return {
          type: 'state_update',
          newState: event.state,
        };

      case 'evaluation_warning':
        return {
          type: 'evaluation_warning',
          results: event.results,
          action: event.action,
        };

      default:
        return { type: 'text', content: '' };
    }
  }
}
