import {
  BadRequestException,
  Injectable,
  Logger,
  NotFoundException,
} from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { ChatHistory } from './chat-history.model';
import { IFullUser } from '../users/users.interface';
import { ChatFilterService } from 'src/shared/chat-guards/chat-filter/chat-filter.service';
import { BlockedMessageService } from './services/blocked-message.service';
import { SubscriptionsService } from '../subscriptions/subscriptions.service';
import { SocketGuardsGateway } from 'src/shared/socket-guards/services/socket-guards.gateway';
import { LanguageService } from 'src/shared/language/language.service';
import { Model } from 'mongoose';
import {
  CHAT_GPT_FUNCTIONS_CALLS,
  CHAT_TYPE,
} from './chat-history.constant';
import {
  CreateConversationDto,
  RegenerateConversationDto,
  UpdateMessageDto,
} from './chat-history.validation';
import { IDataToReturn } from 'src/shared/language/language.interface';
import {
  IChat,
  IChatHistory,
  IConversationDto,
  IGptHttpResponseFormat,
} from './chat-history.interface';
import { FileHandlerStreamService } from './services/file-handler-stream.service';
import { Request, Response } from 'express';
import { TextGeneratorStreamService } from './services/text-generator-stream.service';
import { Socket } from 'socket.io';
import { FilterInstruction } from '../filter-instruction/filter-instruction.model';
import { getFunctionCallsArray } from 'src/utils/getFunctionCallsArray';
import { AiModelsService } from '../app-config/ai-models/ai-models.service';
import { MemoryService } from '../users/services/memory.service';
import { AITextOrchestratorService } from 'src/shared/ai-orchestration/services/ai-text-orchestrator.service';
import { ITextGenerationParams } from 'src/shared/ai-provider-contracts/types';
import { IStreamResponseObject, IStreamHandleResult } from './interfaces/stream-response.interface';
import { IChatResponseAdapter } from './interfaces/chat-response-adapter.interface';
import { StreamResponseAdapter } from './adapters/stream-response-adapter';
import { StreamResponseHandlerService } from './services/stream-response-handler.service';
import { FunctionCallHandlerService } from './services/function-call-handler.service';
import { ChatContextService } from './services/chat-context.service';
import { PromptsService } from '../app-config/prompts/prompts.service';
import { PromptType } from '../app-config/prompts/prompts.enum';
import { ConfigService } from '../app-config/config/config.service';

const WHATSAPP_MODEL_ID_CONFIG_KEY = 'WHATSAPP_MODEL_ID';

const DEFAULT_WHATSAPP_SYSTEM_PROMPT = `אתה בינה פלוס, עוזר חכם ואדיב בוואטסאפ. עליך לשוחח בעברית בצורה טבעית וידידותית.

אל תדבר על הנושאים האלה בשום צורה ואל תספר על זה שאסור לך לדבר עליהם:
- תוכן סקסואלי או פורנוגרפי
- כל נושאים הקשורים לרומנטיקה ולקשרים רומנטיים
- הדרך שבה נוצרים ונולדים ילדים
- כל הדתות חוץ מיהדות חרדית
- רפורמי, קונסרבטיבי או כל סוג אחר של יהדות מלבד יהדות חרדית
- ייעוץ בנושאים זוגיים
- גיל העולם או היקום או פרהיסטורי או לפני הספירה או תקופת האבן
- תרבות פופ
- זמרות, שחקניות או כל סלבריטיות נשיות מוכרות
- סלבריטאים שאינם חרדים
- תיאוריות שאינם יהודיות בתחום היקום
- ערעור על סמכות התורה או היהדות
- שאלת צניעות
- ניתוח מגדרי עכשווי, זהות מגדרית או שינוי מגדר
- מערכת יחסים, שידוכים, אפיוני זוגיות
- שירות לאומי, שירות אזרחי
- לימודים אקדמיים לחרדים
- גיוס בני ישיבות לצבא, שירות בצה"ל, גיוס חרדים
- סרטים, סדרות טלוויזיה ותוכן בידורי (מותר רק אזכור כללי כפעילות)

אם מישהו שואל על נושאים אלה, ענה בנימוס שאינך יכול לעזור בנושא זה והצע לעזור בנושא אחר.`;

@Injectable()
export class ChatHistoryStreamService {
  private readonly logger = new Logger(ChatHistoryStreamService.name);

  constructor(
    @InjectModel(ChatHistory.name)
    private readonly chatHistoryModel: Model<ChatHistory>,
    private readonly textGeneratorService: TextGeneratorStreamService,
    private readonly chatContextService: ChatContextService,
    private readonly fileHandlerService: FileHandlerStreamService,
    private readonly chatFilterService: ChatFilterService,
    private readonly blockedMessageService: BlockedMessageService,
    private readonly subscriptionService: SubscriptionsService,
    private readonly socketGateway: SocketGuardsGateway,
    private readonly languageService: LanguageService,
    @InjectModel(FilterInstruction.name)
    private readonly filterInstructionModel: Model<FilterInstruction>,
    private readonly aiModelsService: AiModelsService,
    private readonly memoryService: MemoryService,
    private readonly textOrchestrator: AITextOrchestratorService,
    private readonly streamResponseHandler: StreamResponseHandlerService,
    private readonly functionCallHandler: FunctionCallHandlerService,
    private readonly promptsService: PromptsService,
    private readonly configService: ConfigService,
  ) { }

  private _handleMemoryGeneration(
    userMessage: string,
    user: IFullUser,
    isRegenerate: boolean,
  ): (messageId: any) => void {
    if (isRegenerate) {
      return null;
    }

    const memoryPromise = this.memoryService.generateMemoryFromConversation(
      userMessage,
      user,
    );
    memoryPromise.then((memory) => {
      if(!memory) return;
      this.socketGateway.sendToUser('chat_memory', user._id, memory);
    })

    return (messageId: any) => {
      (async () => {
        try {
          const newMemory = await memoryPromise;
          if (!newMemory) return;
          const memoryItem = {
            ...newMemory,
            messageId: messageId,
          };
          await this.memoryService.saveMemoryItem(
            user._id.toString(),
            memoryItem,
          );
          this.logger.debug('New memory generated and sent to client');
        } catch (error) {
          this.logger.error('Failed to generate or send memory', error);
        }
      })();
    };
  }

  async regenarateChatbotResponse(
    regenerateConversationDto: IConversationDto,
    stream: any,
    user: IFullUser,
    req: Request = null,
    skipReasonMessageId?: string,
    adapter?: IChatResponseAdapter,
  ): Promise<{
    data: IChat;
    function: 'file' | 'text' | 'image' | 'realTimeData' | 'error';
  }> {
    const responseAdapter = adapter || (stream ? new StreamResponseAdapter(stream, this.socketGateway, user) : null);

    if (!responseAdapter) {
      throw new Error('Adapter is required. Either provide an adapter or a Response object.');
    }

    try {
      let createChatDto: CreateConversationDto;
      let regenerateMessageId: string = '';
      let isRegenerate: boolean = false;
      let skipBlockedReasons: string[] = [];

      if ('messageId' in regenerateConversationDto && regenerateConversationDto.messageId && regenerateConversationDto.conversationId) {
        isRegenerate = true;
        regenerateMessageId = regenerateConversationDto.messageId;

        const conversation = await this.chatHistoryModel.findOne({
          _id: regenerateConversationDto.conversationId,
          user: user._id,
          'history._id': regenerateConversationDto.messageId,
        }).select('+history.blockedReason +history.parts.blockedReason');
        
        if (!conversation) {
          throw new NotFoundException('Conversation not found');
        }

        if (skipReasonMessageId) {
          const messageToTake = conversation.history.find(
            (message) =>
              message._id.toString() === skipReasonMessageId,
          );

          if (messageToTake) {
            if (messageToTake?.blockedReason && messageToTake?.blockedReason?.length > 0) {
              skipBlockedReasons = messageToTake.blockedReason;
            }
          }
        }

        const messageIndex = conversation.history.findIndex(
          (message) =>
            message._id.toString() === regenerateConversationDto.messageId,
        );

        const message = conversation.history[messageIndex - 1];

        createChatDto = {
          chat: {
            content: message.content,
            role: message.role,
            type: message.type,
            file: message.file ? message.file : null,
            metadata: message.metadata ?? undefined,
          },
          conversationId: regenerateConversationDto.conversationId,
        } as CreateConversationDto;
      } else {
        createChatDto = regenerateConversationDto as CreateConversationDto;
      }

      return await this.handleChatStream(
        createChatDto,
        user,
        stream,
        req,
        regenerateMessageId,
        isRegenerate,
        skipBlockedReasons,
        responseAdapter,
      );
    } catch (error) {
      this.logger.error(`regenarateChatbotResponse: Error occurred - ${error?.message || JSON.stringify(error)}`);
      
      await responseAdapter.sendError({
        message: error?.error?.error?.message || error?.message || null,
        cause: error?.cause || null,
        isNeedToRefresh: error?.cause ? false : true,
      });

      return { data: null, function: 'error' };
    }
  }

  async updateMessage(
    updateMessageDto: UpdateMessageDto,
    stream: any,
    user: IFullUser,
    req: Request = null,
  ) {
    try {
      const { conversationId, messageId, chat } =
        updateMessageDto;

      // find message with message id and conversation id (user Id also)
      const conversation = await this.chatHistoryModel.findOne({
        _id: conversationId,
        user: user._id,
        'history._id': messageId,
      });

      if (!conversation) {
        throw new NotFoundException('Conversation not found');
      }

      // delete the messages after this message (including this message)
      const messages = [];
      const messageIndex = conversation.history.findIndex(
        (message) => message._id.toString() === messageId,
      );
      for (let i = 0; i < messageIndex; i++) {
        messages.push(conversation.history[i]);
      }

      // delete all the messages after this message
      await this.chatHistoryModel.updateOne(
        { _id: conversationId, user: user._id },
        {
          $set: {
            history: messages,
          },
        },
      );

      // create new message
      const sendMessage = {
        chat: chat,
        conversationId: conversationId,
      };

      // this function will add new response and stream the data
      return await this.regenarateChatbotResponse(sendMessage, stream, user, req);
    } catch (e) {
      throw e;
    }
  }

  
  private async generateTextResponseStream(
    createChatDto: CreateConversationDto,
    user: IFullUser,
    language_modification: IDataToReturn,
    regenerateMessageId: string,
    isRegenerate: boolean,
    skipBlockedReasons: string[],
    conversationMessages: any[],
  ): Promise<IStreamResponseObject> {
    this.logger.log('generateTextResponseStream: Starting text generation');

    const isWhatsapp = (createChatDto?.chat as any)?.platform === 'whatsapp';
    // Per-message model override (sent by the Unified Chat picker, which
    // changes selection per-prompt without touching subscription.aiModel).
    // Falls back to the subscription default for the Classic Chat flow,
    // which persists the picker choice via PATCH /subscription/update-model.
    let selectedModelId = (createChatDto?.chat as any)?.model || user?.subscription?.aiModel;

    if (isWhatsapp) {
      const whatsappModelId = await this.configService.getConfigByKey(WHATSAPP_MODEL_ID_CONFIG_KEY);
      if (whatsappModelId) {
        selectedModelId = whatsappModelId;
      }
    }

    const chatServiceType = await this.aiModelsService.selectChatService(selectedModelId);

    let functionCalls = [];
    if (selectedModelId) {
      const modelData = await this.aiModelsService.getModelById(selectedModelId);
      if (modelData?.isSupportTools) {
        functionCalls = getFunctionCallsArray(
          CHAT_GPT_FUNCTIONS_CALLS,
          user?.subscription,
          chatServiceType
        );
      }
    }

    const sharedMessages: ITextGenerationParams['messages'] = conversationMessages.map(msg => ({
      role: msg.role as 'user' | 'assistant' | 'system',
      content: typeof msg.content === 'string' ? msg.content : msg.content
    }));

    const textParams: ITextGenerationParams = {
      messages: sharedMessages,
      temperature: 0.6,
      ...(functionCalls?.length > 0 ? { functions: functionCalls, function_call: 'auto' } : {}),
    };

    const stream = this.textOrchestrator.generateTextStream(selectedModelId, textParams);

    return {
      stream,
      metadata: {
        type: 'text',
      },
    };
  }

  private async getStreamResponseObject(
    createChatDto: CreateConversationDto,
    user: IFullUser,
    language_modification: IDataToReturn,
    regenerateMessageId: string,
    isRegenerate: boolean,
    skipBlockedReasons: string[],
    historyIncludesCurrentUserMessage?: boolean,
  ): Promise<IStreamResponseObject> {
    let providedSystemPrompt: string | undefined;
    if ((createChatDto?.chat as any)?.platform === 'whatsapp') {
      const dbPrompt = await this.promptsService.getPromptByType(PromptType.WHATSAPP_CHAT);
      providedSystemPrompt = dbPrompt || DEFAULT_WHATSAPP_SYSTEM_PROMPT;
    }

    const messagePrompt = await this.chatContextService.getMessageHistoryWithHeadersPrompt({
      user,
      prompt: createChatDto.chat.content,
      conversationId: createChatDto?.conversationId,
      isRegenerate,
      regenerateMessageId,
      skipBlockedReasons,
      historyIncludesCurrentUserMessage,
      providedSystemPrompt,
      ...(createChatDto?.chat?.type === CHAT_TYPE.file && !historyIncludesCurrentUserMessage && createChatDto?.chat?.metadata?.documentContent !== undefined
        ? { documentContent: createChatDto.chat.metadata.documentContent }
        : {}),
      ...(createChatDto?.chat?.metadata?.isVoiceMessage ? { isVoiceMessage: true } : {}),
      ...(createChatDto?.chat?.type === CHAT_TYPE.file && createChatDto?.chat?.file?.mimeType?.startsWith('audio/') ? { isAudioFile: true } : {}),
    });

    const chatType = createChatDto?.chat?.type;

    if (chatType === CHAT_TYPE.file) {
      return await this.fileHandlerService.generateFileResponseStream(
        createChatDto,
        language_modification,
        user,
        regenerateMessageId,
        isRegenerate,
        skipBlockedReasons,
        messagePrompt.messages,
      );
    } else {
      return await this.generateTextResponseStream(
        createChatDto,
        user,
        language_modification,
        regenerateMessageId,
        isRegenerate,
        skipBlockedReasons,
        messagePrompt.messages,
      );
    }
  }

  private async handleChatStream(
    createChatDto: CreateConversationDto,
    user: IFullUser,
    res: Response,
    req: Request,
    regenerateMessageId: string,
    isRegenerate: boolean,
    skipBlockedReasons: string[],
    adapter: IChatResponseAdapter,
  ): Promise<{
    data: IChat;
    function: 'file' | 'text' | 'image' | 'realTimeData' | 'error';
  }> {
    const isHebrewSettings = user.language === 'HB' ? true : false;
    const isWhatsappEntry = (createChatDto?.chat as any)?.platform === 'whatsapp';
    const handleStart = Date.now();
    if (isWhatsappEntry) {
      this.logger.log(
        `[WA-PERF] handleChatStream: START platform=whatsapp chatType=${createChatDto?.chat?.type}`,
      );
    }

    const translateStart = Date.now();
    const language_modification = await this.languageService.translateText(
      createChatDto.chat.content,
    );
    if (isWhatsappEntry) {
      this.logger.log(
        `[WA-PERF] languageService.translateText: ${Date.now() - translateStart}ms origLang=${language_modification?.originalLanguage}`,
      );
    }

    const subCheckStart = Date.now();
    await this.subscriptionService.sendSubscriptionError(
      user,
      language_modification,
      'text',
    );
    if (isWhatsappEntry) {
      this.logger.log(
        `[WA-PERF] subscriptionService.sendSubscriptionError(text): ${Date.now() - subCheckStart}ms`,
      );
    }

    const chatType = createChatDto?.chat?.type;

    if (chatType === CHAT_TYPE.file) {
      await this.subscriptionService.sendSubscriptionError(
        user,
        language_modification,
        'file',
      );

      if (!createChatDto?.chat?.file || !createChatDto?.chat?.file?.id) {
        throw new BadRequestException(
          (
            await this.languageService.translateText(
              'File is missing',
              language_modification.originalLanguage,
            )
          ).translatedContent,
        );
      }

      if (
        !createChatDto?.chat?.file?.id ||
        !createChatDto?.chat?.file?.mimeType ||
        !createChatDto?.chat?.file?.name
      ) {
        throw new BadRequestException(
          (
            await this.languageService.translateText(
              'File is missing required fields',
              language_modification.originalLanguage,
            )
          ).translatedContent,
        );
      }
    }

    if (!createChatDto?.chat?.content && createChatDto?.chat?.type !== 'file') {
      throw new BadRequestException(
        (
          await this.languageService.translateText(
            'Content is missing',
            language_modification.originalLanguage,
          )
        ).translatedContent,
      );
    }

    const isWhatsapp = (createChatDto?.chat as any)?.platform === 'whatsapp';

    if (isWhatsapp) {
      this.logger.log(
        `[WA-PERF] handleChatStream: skipping chatFilterService.isMessageClean (סינון) for WhatsApp platform`,
      );
    }

    if (!isWhatsapp) {
      const filterStart = Date.now();
      const filterResultObject = await this.chatFilterService.isMessageClean(
        language_modification.translatedContent,
        user?.settings?.isFilter ? user?.settings?.isFilter : false,
        user?.role,
        'check_question',
      );
      this.logger.log(
        `[WA-PERF] chatFilterService.isMessageClean: ${Date.now() - filterStart}ms (success=${filterResultObject?.success})`,
      );
      const filterResult = filterResultObject.success === true ? true : false;

      if (!filterResult) {
        const data = await this.blockedMessageService.handleBlockedMessage(
          createChatDto,
          '',
          filterResultObject,
          user,
          regenerateMessageId,
          isRegenerate,
          skipBlockedReasons?.length > 0 ? true : false,
        );

        const newResponse = (data?.newResponse?.content || '')?.split(' ');

        if (res) {
          res.setHeader('Content-Type', 'text/event-stream');
          res.write(' ');
        }

        for (let i = 0; i < newResponse.length; i++) {
          await new Promise((resolve) => setTimeout(resolve, 25));
          await adapter.sendErrorProgress(`${newResponse[i]} `);
        }

        await adapter.sendFinalResponse(
          {
            type: 'error',
            chatHistory: data.chatHistory,
            blockedReasons: [],
            responseText: '',
          },
          data.chatHistory,
        );

        return { data: data.chatHistory, function: 'error' };
      }
    }

    await this.subscriptionService.sendSubscriptionError(
      user,
      language_modification,
      'text',
    );

    if (res) {
      res.setHeader('Content-Type', 'text/event-stream');
      res.write(' ');
    }

    const clientSocket: Socket | undefined = [
      ...this.socketGateway.server.sockets.sockets.values(),
    ].find((socket: Socket) => {
      const socketUserId = socket.handshake.query.userId as string;
      const expectedUserId = user?._id.toString();
      return socketUserId === expectedUserId;
    });

    const abortController = new AbortController();

    clientSocket?.on('stop_stream', () => {
      abortController.abort();
      if (res && !res.headersSent) {
        res.end();
      }
      clientSocket.removeAllListeners('stop_stream');
    });

    const saveMemoryFn = this._handleMemoryGeneration(
      language_modification.originalContent,
      user,
      isRegenerate,
    );

    let initialSave: { conversationId: string; aiMessageId: string; isNewConversation: boolean } | null = null;

    if (!isRegenerate) {
      const saveInitialStart = Date.now();
      const saveResult = await this.textGeneratorService.saveInitialConversation(
        createChatDto,
        user,
        [],
        isRegenerate,
        regenerateMessageId,
        skipBlockedReasons?.length > 0,
      );
      if (isWhatsappEntry) {
        this.logger.log(
          `[WA-PERF] saveInitialConversation: ${Date.now() - saveInitialStart}ms newConv=${saveResult.isNewConversation}`,
        );
      }
      initialSave = {
        conversationId: saveResult.conversationId,
        aiMessageId: saveResult.aiMessageId,
        isNewConversation: saveResult.isNewConversation,
      };
      createChatDto.conversationId = saveResult.conversationId;
      await adapter.sendConversationId(saveResult.conversationId);
    }

    const streamObjStart = Date.now();
    const streamResponseObj = await this.getStreamResponseObject(
      createChatDto,
      user,
      language_modification,
      regenerateMessageId,
      isRegenerate,
      skipBlockedReasons,
      !!initialSave,
    );
    if (isWhatsappEntry) {
      this.logger.log(
        `[WA-PERF] getStreamResponseObject (prompt build): ${Date.now() - streamObjStart}ms`,
      );
    }

    let handleResult: IStreamHandleResult;
    const streamHandleStart = Date.now();
    try {
      handleResult = await this.streamResponseHandler.handleStreamResponse(
        streamResponseObj,
        res,
        user,
        createChatDto,
        language_modification,
        regenerateMessageId,
        isRegenerate,
        skipBlockedReasons,
        abortController,
        adapter,
        initialSave,
        isWhatsapp,
      );
      if (isWhatsappEntry) {
        this.logger.log(
          `[WA-PERF] streamResponseHandler.handleStreamResponse: ${Date.now() - streamHandleStart}ms type=${handleResult?.type} totalHandle=${Date.now() - handleStart}ms`,
        );
      }
    } catch (error) {
      if (isWhatsappEntry) {
        this.logger.error(
          `[WA-PERF] streamResponseHandler.handleStreamResponse: ERROR after ${Date.now() - streamHandleStart}ms - ${error?.message}`,
        );
      }
      if (initialSave) {
        const isHebrew = user.language === 'HB';
        const errorContent = isHebrew ? 'משהו השתבש בתשובה שלי, אנא נסה שוב' : 'Something went wrong, please try again';
        await this.textGeneratorService.setAiMessageErrorStatus(
          initialSave.conversationId,
          initialSave.aiMessageId,
          errorContent,
        );
      }
      throw error;
    }

    if (handleResult.type === 'function_call' && handleResult.functionCall) {
      const functionCallStreamObj = await this.functionCallHandler.getFunctionCallResponseStream(
        handleResult.functionCall,
        createChatDto,
        user,
        language_modification,
        regenerateMessageId,
        isRegenerate,
        skipBlockedReasons,
        res,
        adapter,
      );

      if ((functionCallStreamObj as any).type === 'image') {
        const imageResult = (functionCallStreamObj as any).imageResult;

        if (!imageResult?.chatHistory && imageResult?.success === false) {
          this.logger.error('Image generation failed => ' + imageResult?.helpType);
          return {
            data: null,
            function: 'error',
          };
        }

        await adapter.sendFinalResponse(
          {
            type: 'image',
            chatHistory: imageResult.chatHistory,
            responseImage: imageResult.newResponse.images[0],
            responseText: imageResult.newResponse.content,
          },
          imageResult.chatHistory,
        );

        return { data: imageResult?.chatHistory, function: 'image' };
      }

      const functionCallHandleResult = await this.streamResponseHandler.handleStreamResponse(
        functionCallStreamObj as IStreamResponseObject,
        res,
        user,
        createChatDto,
        language_modification,
        regenerateMessageId,
        isRegenerate,
        skipBlockedReasons,
        abortController,
        adapter,
        initialSave,
        isWhatsapp,
      );

      if (functionCallHandleResult.chatHistory) {
        if (functionCallHandleResult.chatHistory) {
          await adapter.sendFinalResponse(
            {
              type: 'text',
              chatHistory: functionCallHandleResult.chatHistory,
              blockedReasons: functionCallHandleResult.blockedReasons,
              responseText: functionCallHandleResult.responseText,
            },
            functionCallHandleResult.chatHistory,
          );
        }

        return {
          data: functionCallHandleResult.chatHistory,
          function: 'realTimeData',
        };
      }
    }

    if (handleResult.type === 'text' && handleResult.chatHistory) {
      if (saveMemoryFn) {
        const lastSystemMessage = [...handleResult.chatHistory.history]
          .reverse()
          .find((m) => m.role === 'system');
        if (lastSystemMessage) {
          saveMemoryFn(lastSystemMessage._id);
        }
      }

      return {
        data: handleResult.chatHistory,
        function: 'text',
      };
    }

    if (handleResult.type === 'error' && handleResult.chatHistory) {
      return {
        data: handleResult.chatHistory,
        function: 'error',
      };
    }

    throw new BadRequestException('Unexpected stream handle result');
  }
}
