import {
  BadRequestException,
  Injectable,
  Logger,
} from '@nestjs/common';
import { ChatGptService } from 'src/shared/third-party/services/chat-gpt/chat-gpt.service';
import { ChatFilterService } from 'src/shared/chat-guards/chat-filter/chat-filter.service';
import { LanguageService } from 'src/shared/language/language.service';
import { RealTimeDataStreamService } from './real-time-data-stream.service';
import {
  CHAT_FUNCTION_CALL_TYPES,
  CHAT_GPT_FUNCTIONS_CALLS,
} from '../chat-history.constant';
import { IFilterBatch } from '../../filter-batch/filter-batch.interface';
import { getModel } from '../utils/getAiModels';
import { AiModelsService } from '../../app-config/ai-models/ai-models.service';
import { AITextOrchestratorService } from 'src/shared/ai-orchestration/services/ai-text-orchestrator.service';
import { ITextGenerationParams } from 'src/shared/ai-provider-contracts/types';
import { IGptHttpResponseFormat } from '../chat-history.interface';
import { ChatContextService } from './chat-context.service';

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

  constructor(
    private readonly languageService: LanguageService,
    private readonly chatFilterService: ChatFilterService,
    private readonly chatGptService: ChatGptService,
    private readonly chatContextService: ChatContextService,
    private readonly aiModelsService: AiModelsService,
    private readonly textOrchestrator: AITextOrchestratorService,
    private readonly realTimeDataService: RealTimeDataStreamService,
  ) {}

  async createChatStreamForAdminTestingV2(
    payload: IFilterBatch & { promptDetails: { prompt: string, expectedResult: 'pass' | 'fail' } },
  ): Promise<{
    result: 'pass' | 'fail';
    reason: string;
    function: 'text' | 'image' | 'realTimeData';
    blockedType: 'question' | 'answer' | null,
    blockedCategories?: string[],
    rawResponse: string | undefined;
  }> {
    try {
      const language_modification = await this.languageService.translateText(
        payload.promptDetails.prompt,
      );

      if (!payload?.promptDetails?.prompt) {
        throw new BadRequestException(
          (
            await this.languageService.translateText(
              'Prompt is missing',
              language_modification.originalLanguage,
            )
          ).translatedContent,
        );
      }

      const questionRole = payload?.questionFilterVersion?.selectedFrom === 'user' ? 'user' : 'admin';
      const extraParams = {
        filterVersion: {
          selectedFrom: payload?.questionFilterVersion?.selectedFrom || undefined,
          version: payload?.questionFilterVersion?.version?.toString(),
        },
      }

      const filterResultObject = await this.chatFilterService.isMessageClean(
        language_modification.translatedContent,
        true,
        questionRole,
        'check_question',
        {
          ...extraParams,
          model: payload?.questionModel,
          temperature: payload?.questionTemperature,
        }
      );

      const filterResult = filterResultObject.success === true ? true : false;

      if (!filterResult) {
        return {
          result: 'fail',
          reason:
            filterResultObject.reason ||
            'Response text failed the filter. The response is not clean',
          function: 'text',
          blockedCategories: filterResultObject.reason ? [filterResultObject.reason] : [],
          blockedType: 'question',
          rawResponse: undefined,
        };
      }

      this.logger.log('Now calling GPT for Chat For Testing Prompt');

      try {
        const messagePrompt =
          await this.chatContextService.getMessageHistoryWithHeadersPrompt({
            user: null,
            prompt: payload.promptDetails.prompt,
            conversationId: null,
            isRegenerate: false,
            regenerateMessageId: null,
            skipBlockedReasons: [],
            providedSystemPrompt: payload.systemPrompt,
          });

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

        let modelId = payload?.questionModel;
        if (!modelId) {
          const providerKey = getModel(payload?.questionModel);
          modelId = await this.aiModelsService.getModelIdByProviderKey(providerKey);
        }
        
        if (!modelId) {
          throw new Error(`Model not found for questionModel: ${payload?.questionModel}`);
        }

        const textParams: ITextGenerationParams = {
          messages: sharedMessages,
          temperature: payload?.questionTemperature || 0.6,
          functions: CHAT_GPT_FUNCTIONS_CALLS,
          function_call: 'auto',
        };

        const initialResponse = await this.textOrchestrator.generateTextStream(modelId, textParams);

        let response: any,
          responseAsText = '',
          functionName = '',
          functionQuery = '',
          isReturnText = false;

        let contentAwaitingFilter = '';
        let filterLimitCount = 0;
        let blockedReasons: string[] = [];
        let isBlocked = false;

        for await (const chunk of initialResponse) {
          const content = chunk.content || '';

          if (chunk.function_call?.name) {
            functionName = chunk.function_call.name;
          }

          responseAsText += chunk.content || '';
          functionQuery += chunk.function_call?.arguments || '';

          if (!functionName && content) {
            isReturnText = true;

            contentAwaitingFilter += content;

            let wordLimit = 30;
            if (contentAwaitingFilter.split(' ').length >= wordLimit) {
              const answerRole = payload?.answerFilterVersion?.selectedFrom === 'user' ? 'user' : 'admin';
              const isPassFilterObject =
                await this.chatFilterService.isMessageClean(
                  contentAwaitingFilter,
                  true,
                  answerRole,
                  'check_answer',
                  {
                    filterVersion: {
                      selectedFrom: payload?.answerFilterVersion?.selectedFrom || undefined,
                      version: payload?.answerFilterVersion?.version?.toString(),
                    },
                    model: payload?.answerModel,
                    temperature: payload?.answerTemperature,
                  }
                );

              const isPassFilter =
                isPassFilterObject.success === true ? true : false;

              if (isPassFilter) {
                let words = contentAwaitingFilter.split(' ');
                contentAwaitingFilter = words.slice(wordLimit).join(' ');
                if (wordLimit === 5) {
                  filterLimitCount++;
                }
              } else {
                isBlocked = true;

                const boldStart = '<b style="color:red; font-weight:bold;">';
                const boldEnd = '</b>';

                const words = contentAwaitingFilter.split(' ');
                const dataToSend = words.slice(0, wordLimit);
                contentAwaitingFilter = words.slice(wordLimit).join(' ');

                responseAsText = responseAsText?.replace(
                  dataToSend?.join(' '),
                  `${boldStart}${dataToSend?.join(' ')}${boldEnd}`,
                );

                blockedReasons.push(isPassFilterObject.reason);

                if (wordLimit === 5) {
                  filterLimitCount++;
                }
              }
            }
          }
        }

        if (isBlocked) {
          return {
            result: 'fail',
            reason: 'Response text failed the filter. The response is not clean',
            function: 'text',
            blockedType: 'answer',
            blockedCategories: blockedReasons,
            rawResponse: responseAsText,
          };
        }

        if (isReturnText) {
          return {
            result: 'pass',
            reason: 'Text message generated successfully',
            function: 'text',
            blockedType: null,
            blockedCategories: [],
            rawResponse: responseAsText || undefined,
          };
        }

        response = this.chatGptService.formatStreamAsHttpResponse({
          choices: [
            {
              index: 0,
              message: {
                role: 'assistant',
                content: responseAsText,
                function_call: {
                  name: functionName,
                  arguments: functionQuery,
                },
              },
              logprobs: null,
              finish_reason:
                !isReturnText && functionName ? 'function_call' : 'stop',
            },
          ],
        });

        const role = payload?.answerFilterVersion?.selectedFrom === 'user' ? 'user' : 'admin';
        const chatData = await this.gptCallFunctionForTestingV2({
          response: response,
          prompt: payload.systemPrompt,
          role: role,
        }, payload?.questionModel, extraParams);

        return {
          ...chatData,
          blockedType: null,
          blockedCategories: [],
          rawResponse: responseAsText || undefined,
        };
      } catch (e) {
        throw e;
      }
    } catch (error) {
      this.logger.error('Error in chat stream => ' + error?.cause || null);

      return {
        result: 'fail',
        reason: 'There was an error in the main function of the chat stream',
        function: 'text',
        blockedType: null,
        blockedCategories: [],
        rawResponse: '',
      };
    }
  }

  private async gptCallFunctionForTestingV2(
    payload: IGptCallFunctionPayloadForTesting,
    model: string,
    extraParams: { temperature?: number; model?: string; filterVersion: { version: string; selectedFrom: "user" | "admin"; }; }
  ): Promise<{
    result: 'pass' | 'fail';
    reason: string;
    function: 'text' | 'image' | 'realTimeData';
    blockedType: string | null;
    blockedCategories: string[];
    rawResponse?: string;
  }> {
    const { response, prompt, role } = payload;
    let data: any;

    const finishReason = response?.choices[0]?.finish_reason;

    this.logger.log(
      'Initial GPT Call for Chat finishReason => ' + finishReason,
    );

    this.logger.log(
      'calling - function_call => ' +
      (response?.choices[0]?.message?.function_call?.name ||
        'No Function Call (Text Message)'),
    );

    if (finishReason === 'function_call') {
      const functionCallData = response?.choices[0]?.message?.function_call;

      switch (functionCallData?.name) {
        case CHAT_FUNCTION_CALL_TYPES.GET_UPTO_DATE_DATA:
          const query = JSON.parse(functionCallData.arguments)?.quary;
          this.logger.log('query => ' + query);

          data =
            await this.realTimeDataService.handleRealTimeDataStreamForTesting(
              query,
              prompt,
              role,
              false,
              [],
              model,
              extraParams
            );

          if (!data?.isSuccess) {
            return {
              result: 'fail',
              reason: 'Real time data function call failed',
              function: 'realTimeData',
              blockedCategories: [],
              blockedType: null,
              rawResponse: '',
            };
          }

          return {
            result: 'pass',
            reason: 'Real time data function call passed',
            function: 'realTimeData',
            blockedCategories: [],
            blockedType: null,
            rawResponse: '',
          };

        case CHAT_FUNCTION_CALL_TYPES.GENERATE_IMAGE:
          return {
            result: 'pass',
            reason: 'Image function call passed',
            function: 'image',
            blockedCategories: [],
            blockedType: null,
            rawResponse: '',
          };

        case CHAT_FUNCTION_CALL_TYPES.GET_DATA_FROM_URL:
          const urlQuery = JSON.parse(functionCallData.arguments)?.url;
          this.logger.log('urlQuery => ' + urlQuery);

          data =
            await this.realTimeDataService.handleRealTimeDataStreamForTesting(
              urlQuery,
              prompt,
              role,
              true,
              [],
              model,
              extraParams
            );

          if (!data?.isSuccess) {
            return {
              result: 'fail',
              reason: 'Real time data function call failed',
              function: 'realTimeData',
              blockedCategories: [],
              blockedType: null,
              rawResponse: '',
            };
          }

          return {
            result: 'pass',
            reason: 'Real time data function call passed',
            function: 'realTimeData',
            blockedCategories: [],
            blockedType: null,
            rawResponse: '',
          };

        default:
          return {
            result: 'pass',
            reason: 'Function call passed',
            function: 'text',
            blockedCategories: [],
            blockedType: null,
            rawResponse: '',
          };
      }
    } else if (finishReason === 'stop') {
      return {
        result: 'pass',
        reason: 'Text message generated successfully',
        function: 'text',
        blockedCategories: [],
        blockedType: null,
        rawResponse: '',
      };
    } else {
      this.logger.log('finishReason 2 => ' + finishReason);
      return {
        result: 'fail',
        reason: 'There was an error in the function call',
        function: 'text',
        blockedCategories: [],
        blockedType: null,
        rawResponse: '',
      };
    }
  }
}

interface IGptCallFunctionPayloadForTesting {
  response: IGptHttpResponseFormat;
  prompt: string;
  role: 'user' | 'admin';
}

