import { Injectable, Logger } from '@nestjs/common';
import { Pinecone } from '@pinecone-database/pinecone';
import { OpenAI } from 'openai';
import { GoogleGenerativeAI } from '@google/generative-ai';
import * as fs from 'fs';
import * as path from 'path';
import pdf from 'pdf-parse';
// import {pd} from 'pdf-parse';
import axios from 'axios';
import * as XLSX from 'xlsx';
import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf';
import { DocxLoader } from '@langchain/community/document_loaders/fs/docx';
// import { DocxLoader } from '@langchain/document_loaders/fs/docx'
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import * as cheerio from 'cheerio';
import { config } from 'dotenv'
import moment from 'moment'
import { ModelService } from '../model/model.service';
config()
@Injectable()
export class PineconeService {
  private genAI: GoogleGenerativeAI;
  private pineconeClient: Pinecone;
  private openaiClient: OpenAI;

  private prompt: any =

    `
  You are an inbound custmore care represntative for henceforth solutions. You have a youthful and cheery personality. Keep your responses as brief as possible
  but make every attempt to keep the coustmer in the chat bot without being rude. Don't make assumptions
  Ask for clarification if a user request is ambiguous.

  You are given a reference context that may contain relevant information for answering the user's question.
  Provide a complete and clear answer, using relatable examples and analogies where possible to make the response easy to understand.
  If you cannot find relevant information in the provided context to answer the question,
  please apologize politely and explain that you don't have enough information to provide a proper answer.
  If the context is helpful, integrate it into your answer and summarize it and don't give the context in the answer;
  if it is not directly relevant, feel free to exclude it.
  Important Note please give answer in just 30 word very short and clear answer
  `;


  // Add a '•' symbol every 5 to 10 words at natural pauses where your response can be split for text to speech.
  //   `
  //   You are an AI-powered chatbot representing Henceforth Solutions Pvt Ltd, a software development firm specializing in custom web and mobile app development,
  //    AI consulting, and advanced tech solutions. Your primary goal is to engage users naturally, provide helpful information, and guide them 
  //    toward meaningful interactions like sharing project details, exploring services, or scheduling consultations.
  // Key Objectives:
  // Engage Naturally: Communicate in a friendly, conversational, and professional tone. Break responses into smaller, easy-to-follow steps.
  // Understand Context: Carefully interpret user queries to provide accurate, concise, and context-aware answers. Use follow-up questions to clarify details if needed.
  // Show Expertise: Provide detailed information about services, technologies, portfolio, and case studies when relevant. Avoid overwhelming the user with technical jargon unless appropriate.
  // Generate Leads: Gently collect essential user details like name, project requirements, and contact information during the conversation.
  // Guide the Flow: Avoid asking too many questions at once. Use pauses, acknowledge user inputs, and adapt your responses to the conversation flow.
  // Behavioral Guidelines:
  // Use warm, human-like expressions like "Sure thing!" or "Sounds exciting!" to make the interaction feel personal.
  // Pause slightly between responses to mimic human typing.
  // Always refer to the user’s previous input for context to create a connected experience.
  // When unsure, politely admit and offer to connect the user to a human team member.

  // `





  //  You are a knowledgeable and friendly assistant who explains complex topics in a simple, engaging way for a non-technical audience.
  //   You are given a reference context that may contain relevant information for answering the user's question.
  //   Provide a complete and clear answer, using relatable examples and analogies where possible to make the response easy to understand.

  //   If you cannot find relevant information in the provided context to answer the question,
  //   please apologize politely and explain that you don't have enough information to provide a proper answer.
  //   If the context is helpful, integrate it into your answer and summarize it and don't give the context in the answer;
  //    if it is not directly relevant, feel free to exclude it.
  //    Important Note please give answer in just 30 word very short and clear answer

  constructor(
    private readonly ModelService: ModelService
  ) {

    this.pineconeClient = new Pinecone({
      apiKey: process.env.PINECONE_API_KEY,
    });

    this.openaiClient = new OpenAI({
      apiKey: process.env.OPENAI_API_KEY,
    });

    this.genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY);
  }

  async addDocument(vectors: any) {
    try {
      const index = this.pineconeClient.Index(process.env.PINECONE_INDEX_NAME);
      const res = await index.upsert(vectors);
      // console.log('res', res);
      return true;
    } catch (error) {
      throw error;
    }
  }

  async deleteDocument() {
    try {
      const index = this.pineconeClient.Index(process.env.PINECONE_INDEX_NAME);
      const res = await index.deleteAll();
      await this.ModelService.documentsModel.deleteMany()
      console.log('res', res);
      return true;
    } catch (error) {
      throw error;
    }
  }

  async embedContent(content: string) {
    try {
      // For embeddings, use the Text Embeddings model
      // const model = this.genAI.getGenerativeModel({
      //   model: 'text-embedding-004',
      // });

      // //   const text = 'The quick brown fox jumps over the lazy dog.';
      // content = content.trim().replace(/\s+/g, ' ');
      // const result = await model.embedContent(content);
      // const embedding = result.embedding;
      // //   console.log(embedding.values.length);
      // return embedding.values;



      content = content.trim().replace(/\s+/g, ' ');

      const model = 'multilingual-e5-large';
      const embeddings = await this.pineconeClient.inference.embed(
        model,
        [content],
        { inputType: 'passage', truncate: 'END' }
      );
      // console.log("embeddings", embeddings[0]?.values);
      return embeddings[0]?.values
    } catch (error) {
      throw error;
    }
  }

  async getDocument(query: string) {
    try {
      // Convert content to an embedding
      console.time('Embedding generation');
      // Convert content to an embedding
      const vector = await this.embedContent(query);
      console.timeEnd('Embedding generation');

      // Upsert the document embedding into Pinecone

      console.time('Vector DB response');
      const index = this.pineconeClient.Index(process.env.PINECONE_INDEX_NAME);
      const res = await index.query({
        vector: vector,
        topK: 5,

        includeMetadata: true,
        // includeValues: true,
        // filter: { genre: { '$eq': 'action' }
      });
      console.timeEnd('Vector DB response');
      //   let context = '';
      //   for (let i = 0; i < res.matches.length; i++) {
      //     const element = res.matches[i];
      //     context += element.metadata.content;
      //   }
      //   console.log('res', res);
      for (let i = 0; i < res.matches.length; i++) {
        const element = res.matches[i];
        // console.log('element', i, 'index', element.score);
      }
      const context = res.matches
        .map((match) => match.metadata.text)
        .join('\n\n');
      return context;
    } catch (error) {
      throw error;
    }
  }

  // async pdfUpload(file: any) {
  //   let folder_path = path.resolve(
  //     __dirname + '/assets/all_pdfs/' + file.originalname
  //   );
  //   try {
  //     fs.writeFileSync(folder_path, file.buffer);

  //     const splitDocs = await this.splitDocs(folder_path);
  //     const batchSize = 1;

  //     for (let i = 0; i < splitDocs.length; i += batchSize) {
  //       const batch = splitDocs.slice(i, i + batchSize);
  //       console.log('batch', batch.length);
  //       const vectors = await Promise.all(
  //         batch.map(async (doc) => {
  //           let content = doc.pageContent.trim().replace(/\s+/g, ' ');
  //           const embedding = await this.embedContent(content);
  //           // console.log("embedding.values.length",embedding);

  //           return {
  //             id: `docs ${+new Date()}`,
  //             values: embedding,
  //             metadata: {
  //               text: content,
  //               // source: doc.metadata.source,
  //               page: doc.metadata.page,
  //             },
  //           };
  //         })
  //       );
  //       console.log('vectors', vectors.length);

  //       this.addDocument(vectors);
  //     }
  //     fs.unlinkSync(folder_path);
  //     return splitDocs;
  //     // const pdfData = await pdf(file.buffer);
  //     // // console.log("pdfData", pdfData);clear

  //     // let string1= await this.trimToMaxWords(pdfData.text)
  //     // await this.addDocument(string1)
  //     // return pdfData.text
  //   } catch (error) {
  //     fs.unlinkSync(folder_path);

  //     throw error;
  //   }
  // }

  async pdfUpload(file: any) {
    let folder_path = path.resolve(
      __dirname + '/assets/all_pdfs/' + file.originalname
    );
    try {
      fs.writeFileSync(folder_path, file.buffer);

      const splitDocs = await this.splitDocs(folder_path);
      const batchSize = 1;
      let data_to_push = [];
      for (let i = 0; i < splitDocs.length; i++) {
        const batch = splitDocs.slice(i, i + batchSize);

        for (let index = 0; index < batch.length; index++) {
          const element = batch[index];
          data_to_push.push(this.addDataToPinecone(element.pageContent));
          if (data_to_push.length === 10) {
            let resolve = await Promise.all(data_to_push);
            // console.log('resolve', resolve[10]);
            data_to_push = [];
          }
        }
      }
      await this.ModelService.documentsModel.create({
        type: "PDF",
        name: file.originalname,
        document_title: file.originalname,
        // url: url
      })
      fs.unlinkSync(folder_path);
      return splitDocs;
    } catch (error) {
      fs.unlink(folder_path, () => {
        // console.log('file deleted');
      });

      throw error;
    }
  }
  async splitDocs(folder_path: any) {
    try {
      const fileExtension = path.extname(folder_path).toLowerCase();
      let loader;
      if (fileExtension === '.pdf') {
        loader = new PDFLoader(folder_path);
      } else if (fileExtension === '.docx') {
        loader = new DocxLoader(folder_path);
      } else {
        throw new Error(`Unsupported file type: ${fileExtension}`);
      }
      const docs = await loader.load();
      //   console.log("docs1", docs);
      const splitter = new RecursiveCharacterTextSplitter({
        chunkSize: 20000,
        chunkOverlap: 200,
      });
      const splitDocs = await splitter.splitDocuments(docs);
      console.log('splitDocs', splitDocs.length);
      console.log('splitDocs', splitDocs[0].pageContent.length);

      return splitDocs;
    } catch (error) {
      throw error;
    }
  }

  async getAnswer(query: string, chat_history?: string) {
    try {
      // console.log("chat_history", chat_history);

      console.time('Total time of vector db');
      let context = await this.getDocument(query);
      console.timeEnd('Total time of vector db');


      // console.log('context', context);
      //   const prompt = `
      //             ${this.prompt}
      //             QUESTION: ${query}
      //             CONTEXT: ${context}
      //             ANSWER:
      //           `.trim();


      // CONTEXT: ${context}
      // console.log("context",context);


      const prompt = `
      ${this.prompt}
      Question: ${query}
      Context: ${context}
      this is our chat history answer accordingly ${chat_history}
              `.trim();

      const model = this.genAI.getGenerativeModel({
        model: 'gemini-1.5-flash',
        generationConfig: {
          // candidateCount: 1,
          // stopSequences: ["x"],
          maxOutputTokens: 200,
          temperature: 0.7,
        },

      });

      console.time('time in gemini response');
      const result = await model.generateContent(prompt);
      console.timeEnd('time in gemini response');


      return {
        answer: result.response.text(),
      };
    } catch (error) {
      throw error;
    }
  }

  async trimToMaxWords(inputString, maxWords = 40000) {
    // // Split the input string by spaces (or other delimiters if needed)
    // const words = inputString.split(/\s+/);

    // // Take the first 200,000 words and join them back into a single string
    // console.log('trimmedString', inputString.length);
    const trimmedString = inputString.slice(0, maxWords);
    // console.log('trimmedString', trimmedString.length);

    return trimmedString;
  }

  async getPrompt() {
    return {
      prompt: this.prompt,
    };
  }

  async editPrompt(prompt: string) {
    this.prompt = prompt;
    return {
      prompt: this.prompt,
    };
  }

  async excelUpload(file: any) {
    let folder_path = path.resolve(
      __dirname + '/assets/all_excell/' + file.originalname
    );
    try {
      // console.log('file', file);

      fs.writeFileSync(folder_path, file.buffer);
      const workbook = XLSX.readFile(folder_path);
      const sheetName = workbook.SheetNames[0];
      const sheet = workbook.Sheets[sheetName];
      const data = XLSX.utils.sheet_to_json(sheet);
      // console.log('data', data);
      let embeddings = [];
      for (const row of data) {
        const content = Object.entries(row)
          .map(([key, value]) => `${key}: "${value}"`)
          .join(', ');

        embeddings.push(content);
      }

      let data_to_push = [];

      for (let i = 0; i < embeddings.length; i++) {
        const content = embeddings[i];
        let embedding = this.addDataToPinecone(content);
        data_to_push.push(embedding);
        if (data_to_push.length === 10) {
          let resolve = await Promise.all(data_to_push);
          // console.log('resolve', resolve[10]);
          data_to_push = [];
        }
        // console.log('i', i);
      }
      await this.ModelService.documentsModel.create({
        type: "EXCEL",
        name: file.originalname,
        document_title: file.originalname,
        // url: url
      })
      fs.unlinkSync(folder_path);
      return data;
    } catch (error) {
      throw error;
    }
  }

  async addDataToPinecone(content: any) {
    try {
      content = content.trim().replace(/\s+/g, ' ');
      const embedding = await this.embedContent(content);
      let vector = [
        {
          id: `docs ${+new Date()}`,
          values: embedding,
          metadata: {
            text: content,
            // source: doc.metadata.source,
            // page: doc.metadata.page,
          },
        },
      ];
      this.addDocument(vector);
      return embedding;
    } catch (error) {
      throw error;
    }
  }

  async scrapAndUpload(url: string, title: string) {
    try {
      const { data: html } = await axios.get(url);
      const $ = cheerio.load(html);

      // Step 2: Extract text content (adjust selector as needed)
      const textContent = $('body').text().trim();
      // console.log('textContent', textContent);

      let embeddings = this.getChunks(textContent);
      // console.log('embeddings', embeddings.length);

      let data_to_push = [];

      for (let i = 0; i < embeddings.length; i++) {
        const content = embeddings[i];
        let embedding = this.addDataToPinecone(content);
        data_to_push.push(embedding);
        if (data_to_push.length === 10) {
          let resolve = await Promise.all(data_to_push);
          // console.log('resolve', resolve[10]);
          data_to_push = [];
        }
        // console.log('i', i);
      }

      await this.ModelService.documentsModel.create({
        type: "URL",
        name: title,
        document_title: title,
        url: url
      })
      return "Document processed successfully"
    } catch (error) {
      throw error;
    }
  }

  async textUpload(text: string, title: string) {
    try {
      let embeddings = this.getChunks(text);

      let data_to_push = [];

      for (let i = 0; i < embeddings.length; i++) {
        const content = embeddings[i];
        let embedding = this.addDataToPinecone(content);
        data_to_push.push(embedding);
        if (data_to_push.length === 10) {
          let resolve = await Promise.all(data_to_push);
          data_to_push = [];
        }
      }
      await this.ModelService.documentsModel.create({
        type: "TEXT",
        name: title,
        document_title: title,
        // url: url
      })
    } catch (error) {
      throw error;
    }
  }


  getChunks = (text: string): string[] => {
    const chunks: string[] = [];
    let start = 0;
    const maxChunkSize = 9000;
    while (start < text.length) {
      let end = start + maxChunkSize;

      // Ensure we don’t cut in the middle of a word
      if (end < text.length) {
        const lastSpace = text.lastIndexOf(' ', end);
        if (lastSpace > start) end = lastSpace;
      }

      chunks.push(text.slice(start, end));
      start = end + 1;
    }

    return chunks;
  };

  async summerizeChat(chat: string) {
    try {
      const prompt = `Summarize the following chat conversation:\n${chat}`
      const model = this.genAI.getGenerativeModel({
        model: 'gemini-1.5-flash',
      });
      const result = await model.generateContent(prompt);
      // console.log(result.response.text());
      return result.response.text()
    } catch (error) {
      throw error;
    }
  }

  async getEmotion(chat_history?: string) {
    try {
      const prompt = `
      You are an AI model analyzing customer interactions. Below is a chat history between a customer and a support agent.
      Your task is to determine the overall customer sentiment at the end of the conversation. Use labels like
      "Customer Satisfied," "Customer Frustrated," "Customer Angry," "Customer Interested,"  "Customer uninterested," 
      or "Customer Indifferent" to summarize the sentiment.
      Be concise and provide only the sentiment label.
      
      Chat History:
      ${chat_history}
      
      What is the overall customer sentiment?`.trim();

      const model = this.genAI.getGenerativeModel({
        model: 'gemini-1.5-flash',
        generationConfig: {
          // candidateCount: 1,
          // stopSequences: ["x"],
          maxOutputTokens: 200,
          temperature: 0.7,
        },
      });

      console.time('time in gemini response');
      const result = await model.generateContent(prompt);
      console.timeEnd('time in gemini response');

      return {
        answer: result.response.text(),
      };
    } catch (error) {
      throw error;
    }
  }


}
