package main

import (
	"context"
	"encoding/json"
	"fmt"
	"net/url"
	"sort"
	"strings"
	"time"

	"github.com/edgedb/edgedb-go"
	"github.com/flosch/pongo2"
	"github.com/gofiber/fiber/v2"
)

func ChatPageHandler(c *fiber.Ctx) error {
	return c.Render("chat", fiber.Map{}, "layouts/main")
}

func LoadChatHandler(c *fiber.Ctx) error {
	deleteLLMtoDelete(c)

	if checkIfLogin(c) {
		isPremium, isBasic := IsCurrentUserSubscribed(c)
		if IsCurrentUserLimiteReached(c, isBasic) && !isPremium {
			return c.SendString(generateLimitReachedChatHTML(c))
		} else if !checkIfHaveKey(c) {
			return c.SendString(generateEnterKeyChatHTML())
		}
		return c.SendString(generateChatHTML(c))
	} else {
		return c.SendString(generateWelcomeChatHTML())
	}
}

func LoadChatInputHandler(c *fiber.Ctx) error {
	if c.Cookies("jade-edgedb-auth-token") == "" {
		out, err := chatInputTmpl.Execute(pongo2.Context{"IsLogin": false, "HaveKey": false, "IsSubscribed": false, "IsLimiteReached": false})
		if err != nil {
			fmt.Println("Error executing chat input template")
			panic(err)
		}
		return c.SendString(out)
	} else {
		isPremium, isBasic := IsCurrentUserSubscribed(c)
		out, err := chatInputTmpl.Execute(pongo2.Context{"IsLogin": checkIfLogin(c), "HaveKey": checkIfHaveKey(c), "IsSubscribed": isPremium, "IsLimiteReached": IsCurrentUserLimiteReached(c, isBasic)})
		if err != nil {
			fmt.Println("Error executing chat input template")
			panic(err)
		}
		return c.SendString(out)
	}
}

type TemplateMessage struct {
	Icon    string
	Content string
	Hidden  bool
	Id      string
	Name    string
	LLMID   string
	Model   string
	ModelID string
}

func generateChatHTML(c *fiber.Ctx) string {
	// Println the name of the current conversation
	var currentConv Conversation
	err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
	SELECT global currentConversation { name }`, &currentConv)
	if err != nil {
		fmt.Println("Error getting current conversation")
		panic(err)
	}

	// Maybe redo that to be area by area because look like shit rn. It come from early stage of dev. It work tho soooo...
	var Messages []Message

	err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Query(edgeCtx, `
	SELECT Message {
		id,
		selected,
		role,
		content,
		date,
		llm : {
			name,
			modelInfo : {
				modelID,
				name,
				company : {
					icon
				}
			}
		}
	  } 
	FILTER .conversation = global currentConversation AND .conversation.user = global currentUser
	ORDER BY .date ASC
    `, &Messages)
	if err != nil {
		fmt.Println("Error getting messages")
		panic(err)
	}

	var user User
	err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
	SELECT global currentUser { id } LIMIT 1
	`, &user)
	if err != nil {
		fmt.Println("Error getting user")
		panic(err)
	}

	htmlString := "<div class='columns is-centered' id='chat-container' ws-connect='/ws?userID=" + user.ID.String() + "'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"

	var templateMessages []TemplateMessage

	for i, message := range Messages {
		if message.Role == "user" {
			htmlContent := markdownToHTML(message.Content)
			userOut, err := userTmpl.Execute(pongo2.Context{"Content": htmlContent, "ID": message.ID.String()})
			if err != nil {
				fmt.Println("Error executing user template")
				panic(err)
			}
			htmlString += userOut
			// Reset NextMessages when a user message is encountered
			templateMessages = []TemplateMessage{}
		} else {
			// For bot messages, add them to NextMessages with only the needed fields
			templateMessage := TemplateMessage{
				Icon:    message.LLM.Model.Company.Icon, // Assuming Icon is a field you want to include from Message
				Content: markdownToHTML(message.Content),
				Hidden:  !message.Selected, // Assuming Hidden is a field you want to include from Message
				Id:      message.ID.String(),
				Name:    message.LLM.Name,
				Model:   message.LLM.Model.Name,
				ModelID: message.LLM.Model.ModelID,
			}
			templateMessages = append(templateMessages, templateMessage)

			// Check if the next message is not a bot or if it's the last message
			if i+1 == len(Messages) || Messages[i+1].Role != "bot" {
				sort.Slice(templateMessages, func(i, j int) bool {
					if !templateMessages[i].Hidden && templateMessages[j].Hidden {
						return true
					}
					if templateMessages[i].Hidden && !templateMessages[j].Hidden {
						return false
					}
					return true
				})

				botOut, err := botTmpl.Execute(pongo2.Context{"Messages": templateMessages, "ConversationAreaId": i})
				if err != nil {
					fmt.Println("Error executing bot template")
					panic(err)
				}
				htmlString += botOut
				htmlString += "<div style='height: 10px;'></div>"
			}
		}
	}

	htmlString += "</div></div>"

	// Render the HTML template with the messages
	return htmlString
}

func GetUserMessageHandler(c *fiber.Ctx) error {
	id := c.FormValue("id")

	messageUUID, _ := edgedb.ParseUUID(id)

	var selectedMessage Message
	err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
		SELECT Message {
			content
		}
		FILTER
			.id = <uuid>$0;
	`, &selectedMessage, messageUUID)
	if err != nil {
		fmt.Println("Error getting message")
		panic(err)
	}

	out, err := userTmpl.Execute(pongo2.Context{"Content": markdownToHTML(selectedMessage.Content), "ID": id})
	if err != nil {
		fmt.Println("Error executing user template")
		panic(err)
	}

	return c.SendString(out)
}

func GetMessageContentHandler(c *fiber.Ctx) error {
	messageId := c.FormValue("id")
	onlyContent := c.FormValue("onlyContent") // To init the text area of the edit message form

	out := GenerateMessageContentHTML(c.Cookies("jade-edgedb-auth-token"), messageId, onlyContent, false)

	return c.SendString(out)
}

func GenerateMessageContentHTML(authCookie string, messageId string, onlyContent string, withDiv bool) string {
	messageUUID, _ := edgedb.ParseUUID(messageId)

	var selectedMessage Message
	err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": authCookie}).QuerySingle(edgeCtx, `
		SELECT Message {
			content,
      area: {
        id,
        position,
      },
			llm : {
				name,
				modelInfo : {
					modelID,
					name,
				}
			}
		}
		FILTER
			.id = <uuid>$0;
	`, &selectedMessage, messageUUID)
	if err != nil {
		panic(err)
	}

	if onlyContent == "true" {
		return markdownToHTML(selectedMessage.Content)
	}

	var out string

	if withDiv {
		out = fmt.Sprintf("<div class='message-content' id='content-%d' style='width: 100%%; overflow-y: hidden'>", selectedMessage.Area.Position)
	} else {
		out = ""
	}

	out += "<div class='message-header'>"
	out += "<p>"
	out += "<strong>" + selectedMessage.LLM.Name + "</strong> <small>" + selectedMessage.LLM.Model.ModelID + "</small>"
	out += "	</p>"
	out += "</div>"
	out += "<div class='message-body'>"
	out += "	<ct class='content'>"
	out += markdownToHTML(selectedMessage.Content)
	out += "	</ct>"
	out += "</div>"

	if withDiv {
		out += "</div>"
	}

	// Update the selected value of messages in the database
	err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": authCookie}).Execute(edgeCtx, `
	WITH m := (SELECT Message FILTER .id = <uuid>$0)
	UPDATE Message
	FILTER .area = m.area
	SET {selected := false};
	`, messageUUID)
	if err != nil {
		panic(err)
	}

	err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": authCookie}).Execute(edgeCtx, `
	UPDATE Message
	FILTER .id = <uuid>$0
	SET {selected := true};
	`, messageUUID)
	if err != nil {
		panic(err)
	}

	return out
}

func generateWelcomeChatHTML() string {
	welcomeMessage, err := welcomeChatTmpl.Execute(pongo2.Context{})
	if err != nil {
		fmt.Println("Error executing welcome chat template")
		panic(err)
	}

	htmlString := "<div class='columns is-centered' id='chat-container'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"

	NextMessages := []TemplateMessage{}
	nextMsg := TemplateMessage{
		Icon:    "icons/bouvai2.png", // Assuming Icon is a field you want to include from Message
		Content: welcomeMessage,
		Hidden:  false, // Assuming Hidden is a field you want to include from Message
		Id:      "0",
		Name:    "JADE",
	}
	NextMessages = append(NextMessages, nextMsg)

	botOut, err := botTmpl.Execute(pongo2.Context{"Messages": NextMessages, "ConversationAreaId": 0, "NotClickable": true, "DontShowName": true})
	if err != nil {
		fmt.Println("Error executing bot template")
		panic(err)
	}
	htmlString += botOut
	htmlString += "<div style='height: 10px;'></div>"
	htmlString += "</div></div>"

	// Render the HTML template with the messages
	return htmlString
}

func generateHelpChatHandler(c *fiber.Ctx) error {
	return c.SendString(generateHelpChatHTML())
}

func generateHelpChatHTML() string {
	out, err := explainLLMconvChatTmpl.Execute(pongo2.Context{})
	if err != nil {
		fmt.Println("Error executing explain LLM and Conversation template")
		panic(err)
	}
	htmlString := "<div class='columns is-centered' id='chat-container'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"

	closeBtn := `
	<div class="is-flex is-justify-content-flex-end">
		<a class="button is-small is-danger is-outlined" hx-get="/loadChat" hx-target="#chat-container" hx-swap="outerHTML"
		hx-trigger="click">
		<span class="icon">
			<i class="fa-solid fa-xmark"></i>
		</span>
		</a>
	</div>`

	NextMessages := []TemplateMessage{}
	nextMsg := TemplateMessage{
		Icon:    "icons/bouvai2.png", // Assuming Icon is a field you want to include from Message
		Content: out + closeBtn,
		Hidden:  false, // Assuming Hidden is a field you want to include from Message
		Id:      "0",
		Name:    "JADE",
	}
	NextMessages = append(NextMessages, nextMsg)

	botOut, err := botTmpl.Execute(pongo2.Context{"Messages": NextMessages, "ConversationAreaId": 0, "NotClickable": true, "DontShowName": true})
	if err != nil {
		fmt.Println("Error executing bot template")
		panic(err)
	}
	htmlString += botOut
	htmlString += "<div style='height: 10px;'></div>"
	htmlString += "</div></div>"

	// Render the HTML template with the messages
	return htmlString
}

func generateEnterKeyChatHTML() string {
	welcomeMessage := `
	<h1>API keys</h1>
	<p class="mt-2">JADE require at least one API key to work. Add one in the settings at the bottom right of the page.</p>
	<p>API keys are unique codes that allow you to access and use different Large Language Model (LLM) providers.</p>
	<p>Most providers offer free credits when you first sign up or per minute, which means you have a balance in your account that you can use to generate messages.</p>
	<p><strong>You pay for the service based on the number of tokens input and generated.</strong> A token is a small unit of text, roughly equivalent to 3 characters. So, a small input and output text will cost very little.</p>
	<h2>Start for free</h2>
	<p>Groq and Google offer free messages per minutes, enough for a conversation. Allowing you to use their models with JADE for free.</p>
	<p>OpenAI and Anthropic offer 5$ of free credits when creating an account. So you can try JADE with their models for free.</p>
  <p>As an example, I have been using the app for 5 months, sending around 1000 messages/month and it cost me 2-3$/month. While using GPT4, Claude 3.5, Codestral, Gemini 1.5 pro, Llama 70b, ect.</p>
	<h2>Get a key</h2>
	<p>To get a key and learn more about the different LLM providers and their offerings, check out their websites:</p>
	<a
  class="button is-small is-primary is-outlined mt-1"
  href="https://platform.openai.com/account/api-keys"
  target="_blank"
>
  Get OpenAI API key
</a>
<a
  class="button is-small is-primary is-outlined mt-1"
  href="https://console.anthropic.com/"
  target="_blank"
>
  Get Anthropic API key
</a>
<a
  class="button is-small is-primary is-outlined mt-1"
  href="https://console.mistral.ai/"
  target="_blank"
>
  Get Mistral API key
</a>
<a
  class="button is-small is-primary is-outlined mt-1"
  href="https://console.groq.com/"
  target="_blank"
>
  Get Groq API key
</a>
<a
  class="button is-small is-primary is-outlined mt-1"
  href="https://aistudio.google.com/app/apikey"
  target="_blank"
>
  Get Google API key
</a>
<a class="button is-small is-primary is-outlined mt-1"
   href="https://build.nvidia.com/explore/discover"
   target="_blank">Get Nvidia NIM API key</a>
<a
  class="button is-small is-primary is-outlined mt-1"
  href="https://docs.perplexity.ai/docs/getting-started"
  target="_blank"
>
  Get Perplexity API key
</a>
<a
  class="button is-small is-primary is-outlined mt-1"
  href="https://fireworks.ai/login"
  target="_blank"
>
  Get Fireworks API key
</a>
<a class="button is-small is-primary is-outlined mt-1"
   href="https://www.together.ai/"
   target="_blank">Get Together AI API key</a>
<a class="button is-small is-primary is-outlined mt-1"
   href="https://platform.deepseek.com/"
   target="_blank">Get DeepSeek API key</a>
  <br />
  <br />
  <p>Note: Key are encrypted and saved on a secure database link only with the app.</p>`

	htmlString := "<div class='columns is-centered' id='chat-container'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"

	NextMessages := []TemplateMessage{}
	nextMsg := TemplateMessage{
		Icon:    "icons/bouvai2.png",
		Content: welcomeMessage,
		Hidden:  false,
		Id:      "0",
		Name:    "JADE",
	}
	NextMessages = append(NextMessages, nextMsg)

	botOut, err := botTmpl.Execute(pongo2.Context{"Messages": NextMessages, "ConversationAreaId": 0, "NotClickable": true, "DontShowName": true})
	if err != nil {
		fmt.Println("Error executing bot template")
		panic(err)
	}
	htmlString += botOut
	htmlString += "<div style='height: 10px;'></div>"
	htmlString += "</div></div>"

	// Render the HTML template with the messages
	return htmlString
}

func generateTermAndServiceHandler(c *fiber.Ctx) error {
	return c.SendString(generateTermAndServiceChatHTML())
}

func generateTermAndServiceChatHTML() string {
	welcomeMessage := `
	<h1 class="mt-2">Terms of Service</h1>              

	<h2>1. Acceptance of Terms</h2>
	<p>By using JADE (the "App"), you agree to be bound by these Terms of Service ("Terms"). If you do not agree to these Terms, please do not use the App.</p>

	<h2>2. Description of Service</h2>
	<p>The App is a chatbot that makes requests to various third-party APIs to provide information and services. The App is provided "as is" and "as available" without any warranties of any kind.</p>

	<h2>3. User Responsibilities</h2>
	<p>- You are responsible for any content you generate or share using the App.</p>
	<p>- You agree not to use the App for any unlawful or harmful activities.</p>

	<h2>4. Authentication and Payment</h2>
	<p>- Authentication for the App is managed by Google and GitHub. We are not responsible for any issues related to authentication, including but not limited to, unauthorized access or data breaches. Please refer to Google and GitHub's respective terms of service and privacy policies for more information.</p>
	<p>- Payments for any services within the App are processed by Stripe. We are not responsible for any issues related to payment processing, including but not limited to, transaction errors or unauthorized transactions. Please refer to Stripe's terms of service and privacy policy for more information.</p>

	<h2>5. Disclaimer of Warranties</h2>
	<p>- The App is provided without warranties of any kind, either express or implied, including but not limited to, implied warranties of merchantability, fitness for a particular purpose, or non-infringement.</p>
	<p>- We do not guarantee the accuracy, completeness, or usefulness of any information provided by the App.</p>

	<h2>6. Limitation of Liability</h2>
	<p>- In no event shall BouvAI, its affiliates, or its licensors be liable for any indirect, incidental, special, consequential, or punitive damages, including but not limited to, loss of profits, data, use, goodwill, or other intangible losses, resulting from (i) your use or inability to use the App; (ii) any unauthorized access to or use of our servers and/or any personal information stored therein; (iii) any bugs, viruses, trojan horses, or the like that may be transmitted to or through our App by any third party; or (iv) any errors or omissions in any content or for any loss or damage incurred as a result of the use of any content posted, emailed, transmitted, or otherwise made available through the App, whether based on warranty, contract, tort (including negligence), or any other legal theory, whether or not we have been informed of the possibility of such damage.</p>

	<h2>7. Data Privacy</h2>
	<p>- We are not responsible for any data leaks or breaches that may occur. Users are advised to use the App at their own risk.</p>
	<p>- Stored data include messages, conversations, usage, bots and keys. I do not strore any personal infos, google and github send me your email, name and avatar as minimum at your first login but I do not save them.</p>

	<h2>8. Changes to the Terms</h2>
	<p>We reserve the right to modify these Terms at any time. Any changes will be effective immediately upon posting the updated Terms on our website or within the App. Your continued use of the App after any such changes constitutes your acceptance of the new Terms.</p>

	<h2>9. Governing Law</h2>
	<p>These Terms shall be governed and construed in accordance with the laws of Luxembourg, without regard to its conflict of law principles.</p>

	<h2>10. Contact Information</h2>
	<p>If you have any questions about these Terms, please contact us at adrien.bouvais@bouvai.com.</p>

	<strong>BouvAI</strong>
  	`

	closeBtn := `
	  <div class="is-flex is-justify-content-flex-end">
	  <a class="button is-small is-danger is-outlined" hx-get="/loadChat" hx-target="#chat-container" hx-swap="outerHTML"
	  hx-trigger="click">
	  <span class="icon">
	  <i class="fa-solid fa-xmark"></i>
  </span>
	  </a>
	  </div>`

	htmlString := "<div class='columns is-centered' id='chat-container'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"

	NextMessages := []TemplateMessage{}
	nextMsg := TemplateMessage{
		Icon:    "icons/bouvai2.png", // Assuming Icon is a field you want to include from Message
		Content: welcomeMessage + closeBtn,
		Hidden:  false, // Assuming Hidden is a field you want to include from Message
		Id:      "0",
		Name:    "JADE",
	}
	NextMessages = append(NextMessages, nextMsg)

	botOut, err := botTmpl.Execute(pongo2.Context{"Messages": NextMessages, "ConversationAreaId": 0, "NotClickable": true, "DontShowName": true})
	if err != nil {
		fmt.Println("Error executing bot template")
		panic(err)
	}
	htmlString += botOut
	htmlString += "<div style='height: 10px;'></div>"
	htmlString += "</div></div>"

	// Render the HTML template with the messages
	return htmlString
}

func generateLimitReachedChatHTML(c *fiber.Ctx) string {
	welcomeMessage := `You have reached the maximum number of messages for a free account. Please upgrade your account to continue using JADE.`

	var result User
	err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, "SELECT global currentUser { stripe_id, email } LIMIT 1;", &result)
	if err != nil {
		fmt.Println("Error getting current user")
		panic(err)
	}

	clientSecretSession := CreateClientSecretSession(c)

	stripeTable := `
	<stripe-pricing-table 
	pricing-table-id="prctbl_1PJAxDP2nW0okNQyY0Q3mbg4"
	publishable-key="pk_live_51OxXuWP2nW0okNQyme1qdwbL535jbMmM1uIUi6U5zcvEUUwKraktmpCzudXNdPSTxlHpw2FbCtxpwbyFFcasQ7aj000tJJGpWW"
	customer-session-client-secret="` + clientSecretSession + `">
	</stripe-pricing-table>`

	htmlString := "<div class='columns is-centered' id='chat-container'><div class='column is-12-mobile is-8-tablet is-6-desktop' id='chat-messages'>"

	NextMessages := []TemplateMessage{}
	nextMsg := TemplateMessage{
		Icon:    "icons/bouvai2.png", // Assuming Icon is a field you want to include from Message
		Content: "<br>" + welcomeMessage + "<br>" + stripeTable,
		Hidden:  false, // Assuming Hidden is a field you want to include from Message
		Id:      "0",
		Name:    "JADE",
	}
	NextMessages = append(NextMessages, nextMsg)

	botOut, err := botTmpl.Execute(pongo2.Context{"Messages": NextMessages, "ConversationAreaId": 0, "NotClickable": true})
	if err != nil {
		fmt.Println("Error executing bot template")
		panic(err)
	}
	htmlString += botOut
	htmlString += "<div style='height: 10px;'></div>"
	htmlString += "</div></div>"

	// Render the HTML template with the messages
	return htmlString
}

// Button actions
func DeleteMessageHandler(c *fiber.Ctx) error {
	messageId := c.FormValue("id")

	messageUUID, err := edgedb.ParseUUID(messageId)
	if err != nil {
		fmt.Println("Error parsing UUID")
		panic(err)
	}

	// Delete all messages
	err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
	WITH
		messageArea := (SELECT Message FILTER .id = <uuid>$0).area
	DELETE Area
		FILTER .position >= messageArea.position AND .conversation = messageArea.conversation;
	`, messageUUID)
	if err != nil {
		fmt.Println("Error deleting messages")
		panic(err)
	}

	return c.SendString(generateChatHTML(c))
}

func GetEditMessageFormHandler(c *fiber.Ctx) error {
	id := c.FormValue("id")
	idUUID, _ := edgedb.ParseUUID(id)

	var message Message
	err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(context.Background(), `
		SELECT Message { content }
		FILTER .id = <uuid>$0;
	`, &message, idUUID)
	if err != nil {
		fmt.Println("Error getting message")
		panic(err)
	}

	// Calculate the number of rows based on the length of the content
	rows := len(strings.Split(message.Content, "\n"))
	if rows < 10 {
		rows = 10
	}

	out, err := messageEditTmpl.Execute(pongo2.Context{"Content": message.Content, "ID": id, "Rows": rows})
	if err != nil {
		fmt.Println("Error executing user template")
		panic(err)
	}
	return c.SendString(out)
}

func RedoMessageHandler(c *fiber.Ctx) error {
	messageId := c.FormValue("id")
	messageUUID, _ := edgedb.ParseUUID(messageId)

	var selectedLLMIds []string
	err := json.Unmarshal([]byte(c.FormValue("selectedLLMIds")), &selectedLLMIds)
	if err != nil {
		fmt.Println("Error unmarshalling selected LLM IDs")
		panic(err)
	}

	var message Message
	err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(context.Background(), `
		SELECT Message { content }
		FILTER .id = <uuid>$0;
	`, &message, messageUUID)
	if err != nil {
		fmt.Println("Error getting message")
		panic(err)
	}

	// Delete messages
	err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
	WITH
		messageArea := (SELECT Message FILTER .id = <uuid>$0).area
	DELETE Area
		FILTER .position > messageArea.position AND .conversation = messageArea.conversation AND .conversation.user = global currentUser;
	`, messageUUID)
	if err != nil {
		fmt.Println("Error deleting messages")
		panic(err)
	}

	return c.SendString(GeneratePlaceholderHTML(c, message.Content, selectedLLMIds, false))
}

func EditMessageHandler(c *fiber.Ctx) error {
	messageId := c.FormValue("id")
	message := c.FormValue("message")
	messageUUID, _ := edgedb.ParseUUID(messageId)

	var selectedLLMIds []string
	err := json.Unmarshal([]byte(c.FormValue("selectedLLMIds")), &selectedLLMIds)
	if err != nil {
		fmt.Println("Error unmarshalling selected LLM IDs")
		panic(err)
	}

	if len(selectedLLMIds) == 0 {
		return c.SendString("")
	}

	// Delete messages
	err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
	WITH
		messageArea := (SELECT Message FILTER .id = <uuid>$0).area
	DELETE Area
		FILTER .position >= messageArea.position AND .conversation = messageArea.conversation AND .conversation.user = global currentUser;
	`, messageUUID)
	if err != nil {
		fmt.Println("Error deleting messages")
		panic(err)
	}

	return c.SendString(GeneratePlaceholderHTML(c, message, selectedLLMIds, true))
}

func ClearChatHandler(c *fiber.Ctx) error {
	// Delete the default conversation
	err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
	DELETE Area
	FILTER .conversation = global currentConversation;
	`)
	if err != nil {
		fmt.Println("Error deleting messages")
		panic(err)
	}

	return c.SendString(generateChatHTML(c))
}

// Popover stuff
func LoadUsageKPIHandler(c *fiber.Ctx) error {
	if !checkIfLogin(c) || !checkIfHaveKey(c) {
		return c.SendString("")
	}

	InputDateID := c.FormValue("month", time.Now().Format("01-2006"))
	offset := c.FormValue("offset")
	IsActive := false

	var InputDate time.Time
	InputDate, err := time.Parse("01-2006", InputDateID)
	if err != nil {
		fmt.Println("Error parsing date")
		panic(err)
	}

	if offset == "-1" {
		InputDate = InputDate.AddDate(0, -1, 0)
		IsActive = true
	} else if offset == "1" {
		InputDate = InputDate.AddDate(0, 1, 0)
		IsActive = true
	}

	type UsageKPI struct {
		Key struct {
			ModelID string `edgedb:"model_id"`
		} `edgedb:"key"`
		TotalCost  float32 `edgedb:"total_cost"`
		TotalCount int64   `edgedb:"total_count"`
	}

	var usages []UsageKPI
	err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Query(edgeCtx, `
	WITH 
		U := (
			SELECT Usage 
			FILTER .user = global currentUser and .date >= <datetime>$0 AND .date < <datetime>$1
		),
		grouped := (
			GROUP U {
				model_id,
				input_cost,
				output_cost,
			} BY .model_id
		)
	SELECT grouped {
		key := .key { model_id },
		total_count := count(.elements),
		total_cost := sum(.elements.input_cost) + sum(.elements.output_cost),
	} FILTER .total_count > 0 ORDER BY .total_cost DESC
	`, &usages, InputDate, InputDate.AddDate(0, 1, 0))
	if err != nil {
		fmt.Println("Error getting usage")
		panic(err)
	}

	BeautifullDate := InputDate.Format("Jan 2006")

	var (
		TotalCount int64
		TotalCost  float32
	)
	for _, usage := range usages {
		TotalCost += usage.TotalCost
		TotalCount += usage.TotalCount
	}

	out, err := usagePopoverTmpl.Execute(pongo2.Context{
		"usages":     usages,
		"TotalCost":  TotalCost,
		"TotalCount": TotalCount,
		"Date":       BeautifullDate,
		"DateID":     InputDate.Format("01-2006"),
		"IsActive":   IsActive,
	})
	if err != nil {
		fmt.Println("Error generating usage")
		panic(err)
	}

	return c.SendString(out)
}

func GenerateModelPopoverHTML(refresh bool, c *fiber.Ctx) string {
	var llms []LLM
	err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Query(edgeCtx, `
		SELECT LLM {
			id,
			name,
			context,
			temperature,
			modelInfo : {
				modelID,
				name,
				company : {
					name,
					icon
				}
			}
		}
		FILTER .user = global currentUser AND .name != 'none' AND .to_delete = false 
		ORDER BY .position
	`, &llms)
	if err != nil {
		fmt.Println("Error loading LLMs")
		panic(err)
	}

	var modelInfos []ModelInfo
	err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Query(edgeCtx, `
		SELECT ModelInfo {
			modelID,
			name,
			company : {
				name,
				icon
			}
		} 
		FILTER .modelID != 'none' AND .company.name != 'huggingface' AND .company IN global currentUser.setting.keys.company
		ORDER BY .company.name ASC THEN .name ASC
	`, &modelInfos)
	if err != nil {
		fmt.Println("Error getting models")
		panic(err)
	}

	isPremium, _ := IsCurrentUserSubscribed(c)

	out, err := modelPopoverTmpl.Execute(pongo2.Context{
		"IsLogin":      checkIfLogin(c),
		"LLMs":         llms,
		"ModelInfos":   modelInfos,
		"DeleteUpdate": refresh,
		"IsPremium":    isPremium,
	})
	if err != nil {
		fmt.Println("Error generating model popover")
		panic(err)
	}
	return out
}

func LoadModelSelectionHandler(c *fiber.Ctx) error {
	if !checkIfLogin(c) || !checkIfHaveKey(c) {
		return c.SendString("")
	}
	return c.SendString(GenerateModelPopoverHTML(false, c))
}

func GenerateConversationPopoverHTML(isActive bool, c *fiber.Ctx) string {
	var conversations []Conversation
	err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Query(edgeCtx, `
		SELECT Conversation {
			name,
			position,
			selected,
			id
		}
		FILTER .user = global currentUser
		ORDER BY .position
	`, &conversations)
	if err != nil {
		fmt.Println("Error loading conversations")
		panic(err)
	}

	selectedIsDefault := false
	for _, conversation := range conversations {
		if conversation.Name == "Default" && conversation.Selected {
			selectedIsDefault = true
		}
	}

	out, err := conversationPopoverTmpl.Execute(pongo2.Context{
		"Conversations":     conversations,
		"IsActive":          isActive,
		"SelectedIsDefault": selectedIsDefault,
	})
	if err != nil {
		fmt.Println("Error generating conversation popover")
		panic(err)
	}
	return out
}

func LoadConversationSelectionHandler(c *fiber.Ctx) error {
	if !checkIfLogin(c) || !checkIfHaveKey(c) {
		return c.SendString("")
	}

	return c.SendString(GenerateConversationPopoverHTML(false, c))
}

func RefreshConversationSelectionHandler(c *fiber.Ctx) error {
	IsActive := c.FormValue("IsActive") == "true"
	return c.SendString(GenerateConversationPopoverHTML(!IsActive, c))
}

func LoadSettingsHandler(c *fiber.Ctx) error {
	if !checkIfLogin(c) {
		return c.SendString("")
	}

	var user User
	err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).QuerySingle(edgeCtx, `
		SELECT User {
			email
		}
		FILTER .id = global currentUser.id
	`, &user)
	if err != nil {
		fmt.Println("Error loading user")
		panic(err)
	}

	// Percent encoding of the email
	user.Email = url.QueryEscape(user.Email)

	stripeSubLink := "https://billing.stripe.com/p/login/6oE6sc0PTfvq1Hi288?prefilled_email=" + user.Email

	openaiExists, anthropicExists, mistralExists, groqExists, googleExists, perplexityExists, fireworksExists, nimExists, togetherExists, deepseekExists := getExistingKeysNew(c)
	isPremium, isBasic := IsCurrentUserSubscribed(c)

	out, err := settingPopoverTmpl.Execute(pongo2.Context{
		"IsLogin":          checkIfLogin(c),
		"OpenaiExists":     openaiExists,
		"AnthropicExists":  anthropicExists,
		"MistralExists":    mistralExists,
		"GroqExists":       groqExists,
		"GoogleExists":     googleExists,
		"NimExists":        nimExists,
		"PerplexityExists": perplexityExists,
		"FireworksExists":  fireworksExists,
		"TogetherExists":   togetherExists,
		"DeepseekExists":   deepseekExists,
		"AnyExists":        fireworksExists || openaiExists || anthropicExists || mistralExists || groqExists || googleExists || perplexityExists || nimExists || togetherExists || deepseekExists,
		"isPremium":        isPremium,
		"isBasic":          isBasic,
		"StripeSubLink":    stripeSubLink,
	})
	if err != nil {
		fmt.Println("Error loading settings")
		panic(err)
	}
	return c.SendString(out)
}

func CreateConversationHandler(c *fiber.Ctx) error {
	name := c.FormValue("conversation-name-input")
	if name == "" {
		name = "New Conversation"
	}

	err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
		WITH
			C := (
				SELECT Conversation
				FILTER .user = global currentUser
			)
		INSERT Conversation {
			name := <str>$0,
			user := global currentUser,
			position := count(C) + 1
		}
	`, name)
	if err != nil {
		fmt.Println("Error creating conversation")
		panic(err)
	}

	return c.SendString(GenerateConversationPopoverHTML(true, c))
}

func DeleteConversationHandler(c *fiber.Ctx) error {
	conversationId := c.FormValue("conversationId")

	conversationUUID, err := edgedb.ParseUUID(conversationId)
	if err != nil {
		fmt.Println("Error parsing UUID")
		panic(err)
	}

	err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
	DELETE Conversation
	FILTER .user = global currentUser AND .id = <uuid>$0;
	`, conversationUUID)
	if err != nil {
		fmt.Println("Error deleting conversation")
		panic(err)
	}

	// Select the default conversation
	err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
	UPDATE Conversation
	FILTER .user = global currentUser AND .name = 'Default'
	SET {
		selected := true
	};
	`)
	if err != nil {
		fmt.Println("Error selecting default conversation")
		panic(err)
	}

	reloadChatTriggerHTML := `
	<hx hx-get="/loadChat" hx-trigger="load once" hx-swap="outerHTML" hx-target="#chat-container" style="display: none;"></hx>
	`

	return c.SendString(GenerateConversationPopoverHTML(true, c) + reloadChatTriggerHTML)
}

func SelectConversationHandler(c *fiber.Ctx) error {
	conversationId := c.FormValue("conversation-id")
	conversationUUID, err := edgedb.ParseUUID(conversationId)
	if err != nil {
		fmt.Println("Error parsing UUID")
		panic(err)
	}

	err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
		UPDATE Conversation
		FILTER .user = global currentUser
		SET {
			selected := false
		};
	`, conversationUUID)
	if err != nil {
		fmt.Println("Error unselecting conversations")
		panic(err)
	}

	err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
		UPDATE Conversation
		FILTER .user = global currentUser AND .id = <uuid>$0
		SET {
			selected := true
		};
	`, conversationUUID)
	if err != nil {
		fmt.Println("Error selecting conversations")
		panic(err)
	}

	return c.SendString(generateChatHTML(c))
}

func ArchiveDefaultConversationHandler(c *fiber.Ctx) error {
	name := c.FormValue("conversation-name-input")

	err := edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
		UPDATE Conversation
		FILTER .user = global currentUser AND .name = 'Default'
		SET {
			name := <str>$0
		};
	`, name)
	if err != nil {
		fmt.Println("Error archiving default conversation")
		panic(err)
	}

	err = edgeGlobalClient.WithGlobals(map[string]interface{}{"ext::auth::client_token": c.Cookies("jade-edgedb-auth-token")}).Execute(edgeCtx, `
		WITH
			C := (
				SELECT Conversation
				FILTER .user = global currentUser
			)
		INSERT Conversation {
			name := "Default",
			user := global currentUser,
			position := count(C) + 1
		}
	`, name)
	if err != nil {
		fmt.Println("Error creating conversation")
		panic(err)
	}

	return c.SendString(GenerateConversationPopoverHTML(true, c))
}
