import { Request, Response } from 'express';
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { genPrompt, type IGenPromptCustomConfig } from '@opentiny/genui-sdk-core';
import { rendererConfig } from '@opentiny/genui-sdk-materials-vue-opentiny-vue/render-config';
import { streamText, stepCountIs } from 'ai';
import getRawBody from 'raw-body';
import { openaiCompatibleTransformChunk } from '@opentiny/genui-sdk-chat-completions';
import type { IOpenaiCompatibleChunk } from '@opentiny/genui-sdk-chat-completions';
import { generateLlmConfig, generateAiSdkTools } from './chat-genui.js';
import { generateJsonPatchPrompt } from './json-patch-prompt.js';
import type { IPlaygroundConfig, LLMConfigParams } from './types/index.js';

type StreamTextOptions = Parameters<typeof streamText>[0];

const getPlaygroundConfig = (playgroundStr: string) => {
  let playgroundConfig: IPlaygroundConfig = {
    mcpServers: [],
    framework: 'Vue',
    promptList: [],
    model: '',
    temperature: 0.3,
    agents: [],
  };

  try {
    playgroundConfig = JSON.parse(playgroundStr);
  } catch (error) {
    console.error('Failed to parse playground from metadata:', error);
  }

  return {
    mcpServers: playgroundConfig.mcpServers || [],
    framework: playgroundConfig.framework || 'Vue',
    userAppendPrompt: playgroundConfig.promptList?.filter(Boolean).join('\n') || '',
    model: playgroundConfig.model || '',
    temperature: playgroundConfig.temperature || 0.3,
  };
};

export const createChatTemplate = () => {
  return {
    chatTemplateHandler: async (req: Request, res: Response) => {
      const abort = new AbortController();
      const body = JSON.parse(await getRawBody(req, { encoding: 'utf-8' }));
      if (process.env.CHAT_UI_REPLAY_MODE === 'true') {
        const text = await fs.readFile(
          path.join(fileURLToPath(import.meta.url), '../../chat-template-replay/replay.txt'),
          'utf-8',
        );
        const data = text.split(/\r?\n\r?\n/);
        for await (const item of data) {
          if (abort.signal.aborted) {
            res.write('data: [ABORTED]\n\n');
            res.end();
            return;
          }
          res.write(item.trim() + '\n\n');
          await new Promise((resolve) => setTimeout(resolve, 200));
        }
        res.end();
        return;
      }

      const { tinygenui: tinygenuiStr, playground: playgroundStr } = body.metadata || {};

      let tgCustomConfig: IGenPromptCustomConfig = {};

      if (tinygenuiStr) {
        try {
          tgCustomConfig = typeof tinygenuiStr === 'string' ? JSON.parse(tinygenuiStr) : {};
        } catch (error) {
          console.error('Failed to parse tinygenui from metadata:', error);
        }
      }

      const playgroundConfig = getPlaygroundConfig(playgroundStr);
      const { mcpServers, framework, userAppendPrompt } = playgroundConfig;

      const llmConfigParams: LLMConfigParams = {
        model: playgroundConfig.model,
        temperature: playgroundConfig.temperature,
        mcpServers,
      };

      const llmConfig = await generateLlmConfig(llmConfigParams);
      const { model, temperature, prompt: customSystemPrompt, specificPrompt, provider, extraBody } = llmConfig;
      const { tools, clientsMap } = await generateAiSdkTools(
        mcpServers.filter((s) => s.enabled),
        abort.signal,
      );
      const maxSteps = 30;
      const systemPrompt = `${genPrompt(rendererConfig, tgCustomConfig)}
      ${body.templateSchema ? generateJsonPatchPrompt() : ''}
      ${specificPrompt}
      ${customSystemPrompt}`;

      const messages = body.messages;
      if (body.templateSchema) {
        const schemaJsonContext = `
          **当前 schemaJson(这是唯一可信的 ID 来源):**
          \`\`\`schemaJson
          ${JSON.stringify(body.templateSchema, null, 2)}
          \`\`\`
          `;
        if (messages.length > 0 && messages[messages.length - 1].role === 'user') {
          if (Array.isArray(messages[messages.length - 1].content)) {
            messages[messages.length - 1].content.push({
              type: 'text',
              text: schemaJsonContext,
            });
          } else {
            messages[messages.length - 1].content += schemaJsonContext;
          }
        } else {
          messages.push({
            role: 'user',
            content: schemaJsonContext,
          });
        }
      }
      const providerOptions =
        provider?.name && extraBody && Object.keys(extraBody).length > 0
          ? ({ [provider.name]: extraBody } as StreamTextOptions['providerOptions'])
          : undefined;

      const options: StreamTextOptions = {
        model,
        temperature,
        system: systemPrompt,
        messages: messages,
        abortSignal: abort.signal,
        tools,
        toolChoice: 'auto',
        stopWhen: stepCountIs(maxSteps),
        ...(providerOptions ? { providerOptions } : {}),
      } as const;

      res.on('close', async () => {
        try {
          abort.abort('/chat-genui connection closed');
        } catch (error) {
          console.error(error);
        } finally {
          for (const client of clientsMap.values()) {
            await client.close();
          }
        }
      });

      try {
        const stream = streamText(options);
        for await (const chunk of stream.fullStream as unknown as AsyncGenerator<IOpenaiCompatibleChunk>) {
          if (abort.signal.aborted) {
            break;
          }
          const newChunk = openaiCompatibleTransformChunk(chunk, { model });

          if (newChunk) {
            res.write('data: ' + JSON.stringify(newChunk) + '\n\n');
          }
        }
      } catch (error: any) {
        res.write('data: [ERROR]\n\n');
        res.end();
        return;
      }

      if (abort.signal.aborted) {
        res.write('data: [ABORTED]\n\n');
      } else {
        res.write('data: [DONE]\n\n');
      }

      res.end();
    },
  };
};