package main

import (
	"bytes"
	"context"
	"ctxGPT/database"
	"fmt"
	"os"
	"strings"
	"text/template"
	"time"

	"github.com/pkoukk/tiktoken-go"
	"github.com/sashabaranov/go-openai"
)

const encodingName = "gpt-4"
const model = openai.GPT4TurboPreview

func main() {

	db, err := database.NewDB()
	if err != nil {
		panic(err)
	}
	defer db.Close()
	value, err := db.Get(context.Background(), "context1")
	if err != nil {
		panic(err)
	}
	fmt.Println(value)

	err = db.Save(context.Background(), "context2", "value2")
	if err != nil {
		panic(err)
	}

	// to get text out of PDF, DOC, DOCX, XML, HTML, RTF, ODT pages documents and images to plain text
	// use https://github.com/sajari/docconv

	summarizeConvoPrompt, err := BuildPrompt("summarize.tmpl", struct{ WordLimit int }{WordLimit: 100})
	if err != nil {
		panic(err)
	}
	fmt.Println(summarizeConvoPrompt)
	tokenCount, err := GetTokenCount(summarizeConvoPrompt)
	if err != nil {
		panic(err)
	}
	fmt.Println(tokenCount)
}

func BuildPrompt(name string, in interface{}) (string, error) {
	fileLocation := "./prompts/" + name
	tmpl, err := template.New(name).ParseFiles(fileLocation)
	if err != nil {
		return "", fmt.Errorf("error parsing template: %w", err)
	}
	b := bytes.Buffer{}
	err = tmpl.Execute(&b, in)
	if err != nil {
		return "", fmt.Errorf("error executing template: %w", err)
	}
	return b.String(), nil
}

func GetTokenCount(input string) (int, error) {
	tke, err := tiktoken.EncodingForModel(encodingName) // cached in "TIKTOKEN_CACHE_DIR"
	if err != nil {
		return 0, fmt.Errorf("error getting encoding: %w", err)
	}
	token := tke.Encode(input, nil, nil)
	return len(token), nil
}

// SinglePromptInteraction calls openai chat endpoint with just a system prompt and a user prompt and returns the response
func SinglePromptInteraction(systemPrompt, prompt string) (openai.ChatCompletionResponse, error) {
	return singlePromptInteraction(systemPrompt, prompt, 5)
}

// singlePromptInteraction calls openai chat endpoint with just a system prompt and a user prompt and returns the response
// it also attempts 5 retries spaced 5 seconds apart in the case of rate limiting errors
func singlePromptInteraction(systemPrompt, prompt string, retries int) (openai.ChatCompletionResponse, error) {

	client := openai.NewClient(os.Getenv("OPENAI_API_KEY"))
	messages := []openai.ChatCompletionMessage{
		{
			Role:    openai.ChatMessageRoleSystem,
			Content: systemPrompt,
		},
		{
			Role:    openai.ChatMessageRoleUser,
			Content: prompt,
		},
	}

	resp, err := client.CreateChatCompletion(
		context.Background(),
		openai.ChatCompletionRequest{
			Model:       model, // switch to the configured Model
			Messages:    messages,
			MaxTokens:   256,
			Temperature: 0,
		},
	)
	if err != nil {
		// if 429, wait and try again
		if strings.Contains(err.Error(), "429") && retries > 0 {
			fmt.Println("429 error, waiting 5 seconds...")
			time.Sleep(5 * time.Second)
			return singlePromptInteraction(systemPrompt, prompt, retries-1) // TODO: establish base case to prevent forever retrying
		}
		return openai.ChatCompletionResponse{}, fmt.Errorf("ChatCompletion request error: %w", err)
	}

	return resp, nil
}

// TODO: anything to be stored in the database should be chunked to sizes between 512 and 1024 tokens
// it should also overlap with the previous chunk by 100-200 tokens
// When the LLM asks for more context, it should be able to use the database to find the most relevant chunks here is how:
// We will get the embeddings for each prompt and use those embeddings to search for the closest 6 chunks
// we will use a separate LLM prompt to make an attempt to select and sort the chunks based on the user's input
// then we will add the best matched chunks to the main prompt as further context for the given prompt
