import { AgentToolContextFactory } from '../agent-tools/agent-tool.types';
import type {
  AiChatServiceContext,
  GenerationInput,
  ModelToolCall,
} from './ai-chat.types';
import {
  A2UI_TOOL_SCHEMAS,
  MAX_TOOL_CALLS_PER_ROUND,
  MAX_TOOL_ROUNDS,
} from './ai-chat.types';
import {
  a2uiReviewSubmitInfo,
  a2uiSubmitInfo,
  buildFormSubmitModelContent,
  buildReviewSubmitModelContent,
} from './ai-chat.submissions';
import { executeTool } from './ai-chat.tools';

export async function executeGeneration(
  context: AiChatServiceContext,
  input: GenerationInput,
): Promise<void> {
  const {
    user,
    conversation,
    userMessage,
    assistant,
    clientRequestId,
    effectiveSkillKey,
    focusContent,
    reasoningEffort,
    signal,
    emit,
    onReady,
  } = input;
  let reasoning = '';
  let content = '';
  try {
    onReady();
    emit('message.created', { message: context.serializeMessage(assistant) });
    for (const attachment of userMessage.attachments ?? []) {
      emit('attachment.processed', {
        messageId: assistant.id,
        attachment: context.attachmentService.serialize(attachment),
      });
    }

    const agentContext = AgentToolContextFactory.fromAuthenticatedUser(user);
    const formSubmit = a2uiSubmitInfo(userMessage.metadata);
    const reviewSubmit = a2uiReviewSubmitInfo(userMessage.metadata);
    let tools = context.toolExecutor.listAvailable(agentContext, effectiveSkillKey).map((tool) => ({
      type: 'function' as const,
      function: {
        name: tool.name,
        description: tool.description,
        parameters: tool.inputSchema ?? {
          type: 'object',
          properties: {},
          additionalProperties: false,
        },
      },
    }));
    if (!formSubmit && !reviewSubmit) {
      tools = tools.filter(
        (tool) =>
          tool.function.name !== 'create_student' && tool.function.name !== 'update_students',
      );
    }
    if (reviewSubmit) {
      tools = tools.filter(
        (tool) =>
          tool.function.name !== 'create_student' &&
          tool.function.name !== 'update_students' &&
          tool.function.name !== 'render_form' &&
          tool.function.name !== 'start_import_wizard',
      );
    }
    tools.push(...A2UI_TOOL_SCHEMAS);
    const runtimeConfig = await context.configService.getRuntimeConfig();
    const config = {
      ...runtimeConfig,
      reasoningEffort: reasoningEffort ?? runtimeConfig.reasoningEffort,
    };
    const modelFocusContent = formSubmit
      ? buildFormSubmitModelContent(formSubmit)
      : reviewSubmit
        ? buildReviewSubmitModelContent(reviewSubmit)
        : focusContent;
    const modelMessages = await context.buildContext(
      conversation.id,
      userMessage.id,
      modelFocusContent,
      effectiveSkillKey,
      config.supportsVision,
    );

    for (let round = 0; round <= MAX_TOOL_ROUNDS; round += 1) {
      context.throwIfAborted(signal);
      let roundContent = '';
      let toolCalls: ModelToolCall[] = [];
      for await (const event of context.modelStream.stream(config, modelMessages, tools, signal)) {
        context.throwIfAborted(signal);
        if (event.type === 'reasoning') {
          reasoning += event.delta;
          context.assertGeneratedLength(reasoning, content);
          emit('reasoning.delta', { messageId: assistant.id, delta: event.delta });
        } else if (event.type === 'content') {
          content += event.delta;
          roundContent += event.delta;
          context.assertGeneratedLength(reasoning, content);
          emit('content.delta', { messageId: assistant.id, delta: event.delta });
        } else if (event.type === 'retrying') {
          emit('model.retrying', {
            messageId: assistant.id,
            retry: {
              attempt: event.attempt,
              maxRetries: event.maxRetries,
              delayMs: event.delayMs,
              reason: event.reason,
            },
          });
        } else {
          toolCalls = event.toolCalls;
        }
      }

      if (!toolCalls.length) break;
      if (round === MAX_TOOL_ROUNDS) {
        const delta = '\n\n本次查询步骤过多，已停止继续调用工具。';
        content += delta;
        emit('content.delta', { messageId: assistant.id, delta });
        break;
      }
      if (toolCalls.length > MAX_TOOL_CALLS_PER_ROUND) {
        const delta = '\n\n模型单轮请求的查询工具过多，已停止执行。';
        content += delta;
        emit('content.delta', { messageId: assistant.id, delta });
        break;
      }

      modelMessages.push({
        role: 'assistant',
        content: roundContent || null,
        tool_calls: toolCalls.map((call) => ({
          id: call.id,
          type: 'function',
          function: { name: call.name, arguments: call.arguments },
        })),
      });
      for (const call of toolCalls) {
        const toolResult = await executeTool(
          context,
          assistant.id,
          call,
          agentContext,
          effectiveSkillKey,
          Boolean(formSubmit),
          Boolean(reviewSubmit),
          user.id,
          emit,
        );
        modelMessages.push({ role: 'tool', tool_call_id: call.id, content: toolResult });
      }
    }

    assistant.content = content;
    assistant.reasoningContent = reasoning || null;
    assistant.status = 'completed';
    assistant.errorCode = null;
    const persistedMetadata = await context.messages.findOne({
      where: { id: assistant.id },
      select: { metadata: true },
    });
    assistant.metadata = {
      ...assistant.metadata,
      ...persistedMetadata?.metadata,
      clientRequestId,
      skillKey: effectiveSkillKey,
      model: config.defaultModel,
      ...((userMessage.attachments ?? []).length
        ? {
            a2uiSources: (userMessage.attachments ?? []).map((attachment) => ({
              title: attachment.originalName,
              url: `/api/ai/chat/attachments/${attachment.id}`,
              description: attachment.mimeType,
            })),
          }
        : {}),
    };
    await context.messages.save(assistant);
    assistant.toolRuns = await context.toolRuns.find({
      where: { messageId: assistant.id },
      order: { id: 'ASC' },
    });
    emit('message.completed', { message: context.serializeMessage(assistant) });
  } catch (error) {
    assistant.content = content;
    assistant.reasoningContent = reasoning || null;
    assistant.status = signal.aborted ? 'cancelled' : 'failed';
    assistant.errorCode = signal.aborted ? 'CLIENT_ABORTED' : context.errorCode(error);
    await context.messages.save(assistant);
    if (signal.aborted) {
      emit('message.cancelled', {
        messageId: assistant.id,
        content,
        reasoningContent: reasoning,
      });
      return;
    }
    throw error;
  }
}
