import {
  Injectable,
  Logger,
  forwardRef,
  Inject,
  OnModuleInit,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import axios from 'axios';
import { FeishuBot } from './entities/feishu-bot.entity';
import { CreateFeishuBotDto } from './dto/create-bot.dto';
import { ChatService } from '../chat/chat.service';
import { ModelConfigService } from '../model-config/model-config.service';
import { UserService } from '../user/user.service';
import { ModelType } from '../types';
import { FeishuWsManager } from './feishu-ws.manager';
import { ConnectionStatus } from './dto/ws-status.dto';
import { FeishuAssessmentService } from './services/feishu-assessment.service';

@Injectable()
export class FeishuService implements OnModuleInit {
  private readonly logger = new Logger(FeishuService.name);
  private readonly feishuApiBase = 'https://open.feishu.cn/open-apis';

  constructor(
    @InjectRepository(FeishuBot)
    private botRepository: Repository<FeishuBot>,
    @Inject(forwardRef(() => ChatService))
    private chatService: ChatService,
    @Inject(forwardRef(() => ModelConfigService))
    private modelConfigService: ModelConfigService,
    @Inject(forwardRef(() => UserService))
    private userService: UserService,
    private wsManager: FeishuWsManager,
    @Inject(forwardRef(() => FeishuAssessmentService))
    private feishuAssessmentService: FeishuAssessmentService,
  ) {}

  onModuleInit(): void {
    // Break circular dep: inject self into manager after module is ready
    this.wsManager.setFeishuService(this);
  }

  // ─── Bot CRUD ────────────────────────────────────────────────────────────────

  async createBot(userId: string, dto: CreateFeishuBotDto): Promise<FeishuBot> {
    const existing = await this.botRepository.findOne({
      where: { userId, appId: dto.appId },
    });

    if (existing) {
      Object.assign(existing, dto);
      return this.botRepository.save(existing);
    }

    const bot = this.botRepository.create({ userId, ...dto });
    return this.botRepository.save(bot);
  }

  async getUserBots(userId: string): Promise<FeishuBot[]> {
    return this.botRepository.find({
      where: { userId },
      relations: ['user'],
    });
  }

  async getBotById(botId: string): Promise<FeishuBot | null> {
    return this.botRepository.findOne({
      where: { id: botId },
      relations: ['user'],
    });
  }

  async getBotByAppId(appId: string): Promise<FeishuBot | null> {
    return this.botRepository.findOne({
      where: { appId },
      relations: ['user'],
    });
  }

  async setBotEnabled(botId: string, enabled: boolean): Promise<FeishuBot> {
    const bot = await this.botRepository.findOne({ where: { id: botId } });
    if (!bot) throw new Error('Bot not found');
    bot.enabled = enabled;
    return this.botRepository.save(bot);
  }

  async deleteBot(userId: string, botId: string): Promise<void> {
    await this.botRepository.delete({ id: botId, userId });
  }

  // ─── Feishu API Calls ─────────────────────────────────────────────────────────

  /**
   * Get or refresh tenant_access_token, cached per bot in DB
   */
  async getValidToken(bot: FeishuBot): Promise<string> {
    if (
      bot.tokenExpiresAt &&
      bot.tenantAccessToken &&
      new Date(bot.tokenExpiresAt) > new Date(Date.now() + 30 * 60 * 1000)
    ) {
      return bot.tenantAccessToken;
    }

    this.logger.log(`Refreshing access token for bot: ${bot.appId}`);
    const { data } = await axios.post<{
      code: number;
      msg: string;
      tenant_access_token: string;
      expire: number;
    }>(`${this.feishuApiBase}/auth/v3/tenant_access_token/internal`, {
      app_id: bot.appId,
      app_secret: bot.appSecret,
    });

    if (data.code !== 0) {
      throw new Error(`Failed to get Feishu token: ${data.msg}`);
    }

    bot.tenantAccessToken = data.tenant_access_token;
    bot.tokenExpiresAt = new Date(Date.now() + data.expire * 1000);
    await this.botRepository.save(bot);

    return data.tenant_access_token;
  }

  /**
   * Send a card message to a Feishu user
   */
  async sendCardMessage(
    bot: FeishuBot,
    receiveIdType: 'open_id' | 'user_id' | 'chat_id',
    receiveId: string,
    card: any,
  ): Promise<string> {
    const token = await this.getValidToken(bot);

    const { data } = await axios.post<{
      code: number;
      msg: string;
      data: { message_id: string };
    }>(
      `${this.feishuApiBase}/im/v1/messages?receive_id_type=${receiveIdType}`,
      {
        receive_id: receiveId,
        msg_type: 'interactive',
        content: JSON.stringify(card),
      },
      { headers: { Authorization: `Bearer ${token}` } },
    );

    if (data.code !== 0) {
      throw new Error(`Failed to send Feishu card: ${data.msg}`);
    }

    return data.data.message_id;
  }

  /**
   * Send a simple text message to a Feishu user
   */
  async sendTextMessage(
    bot: FeishuBot,
    receiveIdType: 'open_id' | 'user_id' | 'chat_id',
    receiveId: string,
    text: string,
  ): Promise<string> {
    const token = await this.getValidToken(bot);

    const { data } = await axios.post<{
      code: number;
      msg: string;
      data: { message_id: string };
    }>(
      `${this.feishuApiBase}/im/v1/messages?receive_id_type=${receiveIdType}`,
      {
        receive_id: receiveId,
        msg_type: 'text',
        content: JSON.stringify({ text }),
      },
      { headers: { Authorization: `Bearer ${token}` } },
    );

    if (data.code !== 0) {
      throw new Error(`Failed to send Feishu message: ${data.msg}`);
    }

    return data.data.message_id;
  }

  /**
   * Update an already-sent message (supports interactive cards)
   */
  async updateMessage(
    bot: FeishuBot,
    messageId: string,
    content: any,
    msgType: 'text' | 'interactive' = 'interactive',
  ): Promise<void> {
    const token = await this.getValidToken(bot);

    const { data } = await axios.patch<{ code: number; msg: string }>(
      `${this.feishuApiBase}/im/v1/messages/${messageId}`,
      { msg_type: msgType, content: JSON.stringify(content) },
      { headers: { Authorization: `Bearer ${token}` } },
    );

    if (data.code !== 0) {
      this.logger.warn(`Failed to update Feishu message: ${data.msg}`);
    }
  }

  /**
   * Build a professional Feishu card
   */
  private buildFeishuCard(
    content: string,
    title = 'AuraK AI 助手',
    isFinal = false,
  ) {
    return {
      config: {
        wide_screen_mode: true,
      },
      header: {
        template: isFinal ? 'blue' : 'orange',
        title: {
          content: title + (isFinal ? '' : ' (正在生成...)'),
          tag: 'plain_text',
        },
      },
      elements: [
        {
          tag: 'div',
          text: {
            content: content || '...',
            tag: 'lark_md',
          },
        },
        {
          tag: 'hr',
        },
        {
          tag: 'note',
          elements: [
            {
              content: `由 AuraK 知识库驱动 · ${new Date().toLocaleTimeString()}`,
              tag: 'plain_text',
            },
          ],
        },
      ],
    };
  }

  // ─── Chunk 5: ChatService RAG Integration ─────────────────────────────────────

  private processedMessages = new Map<
    string,
    { time: number; responseId?: string }
  >();

  /**
   * Check if message is an assessment command
   */
  isAssessmentCommand(message: string): boolean {
    const trimmed = message.trim().toLowerCase();
    const commandPrefixes = ['/assessment', '/测评', '/eval', '/测评评估'];
    return commandPrefixes.some((prefix) =>
      trimmed.startsWith(prefix.toLowerCase()),
    );
  }

  async handleIncomingMessage(
    bot: FeishuBot,
    openId: string,
    messageId: string,
    userText: string,
  ): Promise<void> {
    // Strip Feishu AT tags and trim
    userText = userText.replace(/<at [^>]*><\/at>/g, '').trim();
    if (!userText) return;

    // 1. Deduplication: check if we are already processing this message
    const now = Date.now();
    const existing = this.processedMessages.get(messageId);
    if (existing && now - existing.time < 1000 * 60 * 10) {
      this.logger.warn(`Ignoring duplicate Feishu message: ${messageId}`);
      return;
    }

    // Mark as being processed
    this.processedMessages.set(messageId, { time: now });

    // Cleanup old cache (simple)
    if (this.processedMessages.size > 1000) {
      for (const [key, val] of this.processedMessages) {
        if (now - val.time > 1000 * 60 * 30) this.processedMessages.delete(key);
      }
    }

    try {
      // Check if message is an assessment command
      if (this.isAssessmentCommand(userText)) {
        this.logger.log(
          `Routing assessment command [${messageId}] for bot ${bot.appId}`,
        );
        // Delegate to assessment service
        await this.feishuAssessmentService.handleCommand(bot, openId, userText);
      } else {
        // Delegate to standard RAG pipeline
        await this.processChatMessage(bot, openId, messageId, userText, true);
      }
    } catch (error) {
      this.logger.error(
        `Message routing failed [${messageId}]: ${error.message}`,
        error,
      );
      try {
        await this.sendTextMessage(
          bot,
          'open_id',
          openId,
          '抱歉，处理您的消息时遇到了错误，请稍后重试。',
        );
      } catch (sendError) {
        this.logger.error('Failed to send error message to Feishu', sendError);
      }
    }
  }

  /**
   * Process a user message via the AuraK RAG pipeline and send the result back.
   * This is the core of the Feishu integration.
   */
  async processChatMessage(
    bot: FeishuBot,
    openId: string,
    messageId: string,
    userMessage: string,
    alreadyDeduplicated = false,
  ): Promise<void> {
    this.logger.log(
      `Processing Feishu message [${messageId}] for bot ${bot.appId}`,
    );
    const now = Date.now();

    if (!alreadyDeduplicated) {
      // 1. Deduplication: check if we are already processing this message
      const now = Date.now();
      const existing = this.processedMessages.get(messageId);
      if (existing && now - existing.time < 1000 * 60 * 10) {
        this.logger.warn(`Ignoring duplicate Feishu message: ${messageId}`);
        return;
      }
      // Mark as being processed
      this.processedMessages.set(messageId, { time: now });
    }

    // Cleanup old cache (simple)
    if (this.processedMessages.size > 1000) {
      for (const [key, val] of this.processedMessages) {
        if (now - val.time > 1000 * 60 * 30) this.processedMessages.delete(key);
      }
    }

    // Get user from bot owner
    const userId = bot.userId;
    const user = await this.userService.findOneById(userId);
    const tenantId = user?.tenantId || 'default';
    const language = user?.userSetting?.language || 'zh';

    // Get the user's default LLM model
    const llmModel = await this.modelConfigService.findDefaultByType(
      tenantId,
      ModelType.LLM,
    );

    if (!llmModel) {
      await this.sendTextMessage(
        bot,
        'open_id',
        openId,
        '❌ 请先在 AuraK 中配置 LLM 模型才能使用机器人。',
      );
      return;
    }

    // Send initial "thinking" card
    const cardTitle = 'AuraK 知识检索';
    const initialCard = this.buildFeishuCard(
      '⏳ 正在检索知识库，请稍候...',
      cardTitle,
      false,
    );
    const msgId = await this.sendCardMessage(
      bot,
      'open_id',
      openId,
      initialCard,
    );

    // Save the response message ID for potential future deduplication debugging
    this.processedMessages.set(messageId, { time: now, responseId: msgId });

    // Run the RAG pipeline in the background so we don't block the Feishu event handler
    // This prevents Feishu from retrying the event if it takes > 3s.

    // Handle knowledge source selection
    let selectedFiles: string[] | undefined = undefined;
    let selectedGroups: string[] | undefined = undefined;

    if (bot.knowledgeBaseId) {
      selectedFiles = [bot.knowledgeBaseId];
    } else if (bot.knowledgeGroupId) {
      selectedGroups = [bot.knowledgeGroupId];
    }

    this._runRagBackground(
      bot,
      msgId,
      userMessage,
      userId,
      llmModel,
      language,
      tenantId,
      cardTitle,
      selectedFiles,
      selectedGroups,
    );
  }

  /**
   * Internal background task for RAG processing
   */
  private async _runRagBackground(
    bot: FeishuBot,
    msgId: string,
    userMessage: string,
    userId: string,
    llmModel: any,
    language: string,
    tenantId: string,
    cardTitle: string,
    selectedFiles?: string[],
    selectedGroups?: string[],
  ) {
    let fullResponse = '';
    let lastUpdateTime = Date.now();
    const UPDATE_INTERVAL = 1500;

    try {
      // Stream from ChatService RAG pipeline
      const stream = this.chatService.streamChat(
        userMessage,
        [],
        userId,
        llmModel,
        language,
        undefined,
        undefined,
        undefined,
        undefined,
        false,
        undefined,
        undefined,
        undefined,
        10,
        0.7,
        undefined,
        undefined,
        undefined,
        tenantId,
      );

      for await (const chunk of stream) {
        if (chunk.type === 'content') {
          fullResponse += chunk.data;

          const now = Date.now();
          if (
            now - lastUpdateTime > UPDATE_INTERVAL &&
            fullResponse.length > 50
          ) {
            const loadingCard = this.buildFeishuCard(
              fullResponse,
              cardTitle,
              false,
            );
            await this.updateMessage(bot, msgId, loadingCard);
            lastUpdateTime = now;
          }
        }
      }
    } catch (err) {
      this.logger.error('RAG stream error for Feishu message', err);
      fullResponse = `抱歉，处理您的问题时遇到了错误：${err?.message || '未知错误'}。`;
    }

    const MAX_LENGTH = 4500;
    const finalContent =
      fullResponse.length > MAX_LENGTH
        ? fullResponse.substring(0, MAX_LENGTH) + '\n\n...(内容过长，已截断)'
        : fullResponse || '抱歉，未能生成有效回复，请稍后再试。';

    const finalCard = this.buildFeishuCard(finalContent, cardTitle, true);
    await this.updateMessage(bot, msgId, finalCard);
  }

  // ─── WebSocket Connection Management ─────────────────────────────────────────

  /**
   * Start WebSocket connection for a bot
   */
  async startWsConnection(botId: string): Promise<void> {
    const bot = await this.getBotById(botId);
    if (!bot) throw new Error('Bot not found');
    if (!bot.enabled) throw new Error('Bot is disabled');

    bot.useWebSocket = true;
    await this.botRepository.save(bot);

    await this.wsManager.connect(bot);
  }

  /**
   * Stop WebSocket connection for a bot
   */
  async stopWsConnection(botId: string): Promise<void> {
    const bot = await this.getBotById(botId);
    if (!bot) throw new Error('Bot not found');

    bot.useWebSocket = false;
    await this.botRepository.save(bot);

    await this.wsManager.disconnect(botId);
  }

  /**
   * Get WebSocket connection status for a specific bot
   */
  async getWsStatus(botId: string): Promise<ConnectionStatus | null> {
    return this.wsManager.getStatus(botId);
  }

  /**
   * Get all WebSocket connection statuses
   */
  async getAllWsStatuses(): Promise<ConnectionStatus[]> {
    return this.wsManager.getAllStatuses();
  }
}
