/**
 * Base Specialist Agent Service
 * 专家 Agent 基类 — 封装调用 Claude API 的通用逻辑
 *
 * 每个专家 Agent 继承此基类，只需定义：
 * 1. System Prompt
 * 2. 可用工具
 * 3. 输入/输出格式转换
 */

import { Logger } from '@nestjs/common';
import Anthropic from '@anthropic-ai/sdk';
import {
  AgentConfig,
  AgentModel,
  ToolDefinition,
  AgentExecutionRecord,
  SpecialistAgentType,
} from '../types/agent.types';

/** Tool executor for specialist's own tools */
export type SpecialistToolExecutor = (
  toolName: string,
  toolInput: Record<string, unknown>,
) => Promise<string>;

/** Specialist execution options */
export interface SpecialistExecutionOptions {
  /** Additional context to prepend to the user message */
  additionalContext?: string;
  /** Override max tokens for this call */
  maxTokensOverride?: number;
  /** Abort signal */
  abortSignal?: AbortSignal;
}

export abstract class BaseSpecialistService {
  protected readonly logger: Logger;
  protected anthropicClient: Anthropic;

  constructor(
    protected readonly config: AgentConfig,
    anthropicClient: Anthropic,
  ) {
    this.logger = new Logger(this.constructor.name);
    this.anthropicClient = anthropicClient;
  }

  /** Subclass must define the system prompt */
  protected abstract getSystemPrompt(): string;

  /** Subclass must define available tools */
  protected abstract getTools(): ToolDefinition[];

  /** Subclass must provide tool executor */
  protected abstract executeTool(
    toolName: string,
    toolInput: Record<string, unknown>,
  ): Promise<string>;

  /**
   * Execute the specialist agent
   * Returns the final text response after internal tool loops
   */
  async execute(
    userMessage: string,
    options?: SpecialistExecutionOptions,
  ): Promise<{ result: string; record: AgentExecutionRecord }> {
    const startedAt = new Date();
    let totalInputTokens = 0;
    let totalOutputTokens = 0;
    let toolCallsCount = 0;

    const fullMessage = options?.additionalContext
      ? `${options.additionalContext}\n\n${userMessage}`
      : userMessage;

    let messages: Array<{ role: 'user' | 'assistant'; content: any }> = [
      { role: 'user', content: fullMessage },
    ];

    const systemPrompt = this.getSystemPrompt();
    const tools = this.getTools();
    const maxTokens = options?.maxTokensOverride || this.config.maxTokens;

    this.logger.debug(
      `[${this.config.type}] Starting execution with ${tools.length} tools, max ${this.config.maxTurns} turns`,
    );

    let finalText = '';

    try {
      // Internal tool loop (like a mini agentLoop)
      for (let turn = 0; turn < this.config.maxTurns; turn++) {
        // Check abort
        if (options?.abortSignal?.aborted) {
          this.logger.debug(`[${this.config.type}] Aborted by signal`);
          break;
        }

        // Call Claude API (non-streaming for specialists — simpler and sufficient)
        const response = await this.callClaude(
          systemPrompt,
          messages,
          tools.length > 0 ? tools.map(t => ({
            name: t.name,
            description: t.description,
            input_schema: t.input_schema,
          })) : undefined,
          maxTokens,
        );

        // Track tokens
        totalInputTokens += response.usage.input_tokens;
        totalOutputTokens += response.usage.output_tokens;

        // Extract text content
        const textBlocks = response.content.filter(
          (b: any) => b.type === 'text',
        );
        const toolUseBlocks = response.content.filter(
          (b: any) => b.type === 'tool_use',
        );

        // Collect text
        for (const block of textBlocks) {
          finalText += (block as any).text;
        }

        // If no tool use, we're done
        if (toolUseBlocks.length === 0 || response.stop_reason === 'end_turn') {
          this.logger.debug(
            `[${this.config.type}] Completed at turn ${turn + 1} with ${toolCallsCount} tool calls`,
          );
          break;
        }

        // Process tool calls
        const toolResults: Array<{
          type: 'tool_result';
          tool_use_id: string;
          content: string;
        }> = [];

        for (const toolUse of toolUseBlocks) {
          const tu = toolUse as any;
          toolCallsCount++;

          this.logger.debug(
            `[${this.config.type}] Tool call: ${tu.name} (turn ${turn + 1})`,
          );

          try {
            const result = await this.executeTool(tu.name, tu.input);
            toolResults.push({
              type: 'tool_result',
              tool_use_id: tu.id,
              content: result,
            });
          } catch (error) {
            const errMsg = error instanceof Error ? error.message : String(error);
            toolResults.push({
              type: 'tool_result',
              tool_use_id: tu.id,
              content: `<tool_use_error>Error: ${errMsg}</tool_use_error>`,
            });
          }
        }

        // Append assistant message and tool results for next turn
        messages = [
          ...messages,
          { role: 'assistant', content: response.content },
          { role: 'user', content: toolResults },
        ];

        // Reset finalText — we want the text from the LAST response
        finalText = '';
      }

      const record: AgentExecutionRecord = {
        agentType: this.config.type,
        startedAt,
        completedAt: new Date(),
        durationMs: Date.now() - startedAt.getTime(),
        inputTokens: totalInputTokens,
        outputTokens: totalOutputTokens,
        toolCallsCount,
        success: true,
      };

      return { result: finalText || '(No response generated)', record };
    } catch (error) {
      const errMsg = error instanceof Error ? error.message : String(error);
      this.logger.error(`[${this.config.type}] Execution failed: ${errMsg}`);

      const record: AgentExecutionRecord = {
        agentType: this.config.type,
        startedAt,
        completedAt: new Date(),
        durationMs: Date.now() - startedAt.getTime(),
        inputTokens: totalInputTokens,
        outputTokens: totalOutputTokens,
        toolCallsCount,
        success: false,
        error: errMsg,
      };

      return {
        result: `Agent ${this.config.type} encountered an error: ${errMsg}`,
        record,
      };
    }
  }

  /**
   * Call Claude API with timeout
   */
  private async callClaude(
    systemPrompt: string,
    messages: Array<{ role: 'user' | 'assistant'; content: any }>,
    tools: any[] | undefined,
    maxTokens: number,
  ): Promise<Anthropic.Message> {
    const timeoutPromise = new Promise<never>((_, reject) => {
      setTimeout(
        () => reject(new Error(`Agent ${this.config.type} timed out after ${this.config.timeoutMs}ms`)),
        this.config.timeoutMs,
      );
    });

    const apiCall = this.anthropicClient.messages.create({
      model: this.config.model,
      system: [{ type: 'text' as const, text: systemPrompt, cache_control: { type: 'ephemeral' as const } }],
      messages,
      ...(tools && tools.length > 0 ? { tools } : {}),
      max_tokens: maxTokens,
      ...(this.config.temperature !== undefined ? { temperature: this.config.temperature } : {}),
    });

    return Promise.race([apiCall, timeoutPromise]);
  }
}
