import { Logger } from '@nestjs/common';
import { EngineStreamEvent, EngineTaskParams } from '../../../domain/ports/outbound/agent-engine.port';
import { ToolExecutor } from './tool-executor';
import Anthropic from '@anthropic-ai/sdk';

interface AnthropicMessage {
  role: 'user' | 'assistant';
  content: string | AnthropicContentBlock[];
}

interface AnthropicContentBlock {
  type: string;
  text?: string;
  id?: string;
  name?: string;
  input?: Record<string, unknown>;
}

interface AnthropicToolResultBlock {
  type: 'tool_result';
  tool_use_id: string;
  content: string;
}

/**
 * Encapsulates the Claude API agent loop logic.
 * Extracted from ClaudeApiEngine to allow independent testing
 * and reuse of the multi-turn tool-use loop.
 */
export class AgentLoop {
  private readonly logger = new Logger(AgentLoop.name);

  constructor(
    private readonly client: Anthropic,
    private readonly toolExecutor: ToolExecutor,
  ) {}

  /**
   * Runs the agent loop: sends messages to the API, processes tool calls,
   * and yields EngineStreamEvents for each step.
   */
  async *run(
    params: EngineTaskParams,
    abortSignal?: AbortSignal,
  ): AsyncGenerator<EngineStreamEvent> {
    const tools = this.buildToolDefinitions(params.allowedTools);

    const messages: AnthropicMessage[] = [
      { role: 'user', content: params.prompt },
    ];

    let totalTokensUsed = 0;
    let turnCount = 0;

    while (turnCount < params.maxTurns) {
      turnCount++;

      // Check for abort before each API call
      if (abortSignal?.aborted) {
        yield { type: 'error', message: 'Task was cancelled', code: 'TASK_CANCELLED' };
        return;
      }

      const requestParams: any = {
        model: 'claude-sonnet-4-5-20250929',
        max_tokens: 8192,
        messages,
      };

      if (params.systemPrompt) {
        requestParams.system = params.systemPrompt;
      }

      if (tools.length > 0) {
        requestParams.tools = tools;
      }

      const response = await this.client.messages.create(requestParams, {
        signal: abortSignal as any,
      });

      // Track token usage
      if (response.usage) {
        totalTokensUsed +=
          (response.usage.input_tokens ?? 0) +
          (response.usage.output_tokens ?? 0);
      }

      // Process content blocks from the response
      const contentBlocks = response.content as AnthropicContentBlock[];
      const toolUseBlocks: Array<{
        id: string;
        name: string;
        input: Record<string, unknown>;
      }> = [];

      for (const block of contentBlocks) {
        if (block.type === 'text' && block.text) {
          yield { type: 'text', content: block.text };
        } else if (block.type === 'tool_use') {
          yield {
            type: 'tool_use',
            toolName: block.name!,
            input: (block.input as Record<string, unknown>) ?? {},
          };
          toolUseBlocks.push({
            id: block.id!,
            name: block.name!,
            input: (block.input as Record<string, unknown>) ?? {},
          });
        }
      }

      // Check stop reason
      if (response.stop_reason === 'end_turn' || toolUseBlocks.length === 0) {
        const summaryBlock = contentBlocks.find(
          (b) => b.type === 'text' && b.text,
        );
        const summary = summaryBlock?.text ?? 'Task completed';

        yield {
          type: 'completed',
          summary,
          tokensUsed: totalTokensUsed,
        };
        return;
      }

      // Push assistant message with all content blocks
      messages.push({ role: 'assistant', content: contentBlocks });

      // Execute tools and collect results
      const toolResults: AnthropicToolResultBlock[] = [];
      for (const toolUse of toolUseBlocks) {
        const result = await this.toolExecutor.execute(
          toolUse.name,
          toolUse.input as Record<string, any>,
        );

        toolResults.push({
          type: 'tool_result' as const,
          tool_use_id: toolUse.id,
          content: result.output,
        });

        yield {
          type: 'tool_result',
          toolName: toolUse.name,
          output: result.output,
          isError: result.isError,
        };
      }

      // Add tool results as user message to continue the conversation
      messages.push({ role: 'user', content: toolResults });
    }

    // Max turns reached
    yield {
      type: 'completed',
      summary: `Agent loop completed after reaching max turns (${params.maxTurns})`,
      tokensUsed: totalTokensUsed,
    };
  }

  /**
   * Builds Anthropic tool definitions from an array of tool name strings.
   */
  private buildToolDefinitions(
    allowedTools: string[],
  ): Array<{
    name: string;
    description: string;
    input_schema: Record<string, unknown>;
  }> {
    if (!allowedTools || allowedTools.length === 0) return [];

    return allowedTools.map((toolName) => ({
      name: toolName,
      description: `Tool: ${toolName}`,
      input_schema: {
        type: 'object' as const,
        properties: {},
      },
    }));
  }
}
