package main

import (
	"fmt"
	"himbot/command"
	"himbot/lib"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/bwmarrin/discordgo"
	"github.com/joho/godotenv"
)

var (
	commands        []*discordgo.ApplicationCommand
	commandHandlers map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate)
)

func main() {
	godotenv.Load(".env")

	// Load configuration
	config := lib.LoadConfig()

	// Initialize commands and handlers with config
	initCommands(config)
	initCommandHandlers(config)

	if err := command.InitBard("datasets/bard.gob"); err != nil {
		log.Printf("Failed to load Bard dataset: %v", err)
	}

	err := lib.InitDB()
	if err != nil {
		log.Fatalf("Failed to initialize database: %v", err)
	}

	if config.DiscordToken == "" {
		log.Fatalln("No $DISCORD_TOKEN given.")
	}

	dg, err := discordgo.New("Bot " + config.DiscordToken)
	if err != nil {
		log.Fatalf("Error creating Discord session: %v", err)
	}

	dg.AddHandler(ready)
	dg.AddHandler(interactionCreate)

	processorManager := lib.NewMessageProcessorManager()

	// Register processors
	processorManager.RegisterProcessor(lib.ProcessHimbucks)

	dg.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
		processorManager.ProcessMessage(s, m)
	})

	dg.Identify.Intents = discordgo.IntentsGuilds | discordgo.IntentsGuildMessages

	err = dg.Open()
	if err != nil {
		log.Fatalf("Error opening connection: %v", err)
	}

	log.Println("Bot is now running. Press CTRL-C to exit.")
	registerCommands(dg)

	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
	<-sc

	log.Println("Shutting down gracefully...")

	if lib.DBClient != nil {
		// Close prepared statements
		lib.CleanupDB()
		lib.DBClient.Close()
	}

	dg.Close()
}

func ready(s *discordgo.Session, event *discordgo.Ready) {
	log.Printf("Logged in as: %v#%v", s.State.User.Username, s.State.User.Discriminator)
}

func interactionCreate(s *discordgo.Session, i *discordgo.InteractionCreate) {
	if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok {
		h(s, i)
	}
}

func registerCommands(s *discordgo.Session) {
	log.Println("Checking command registration...")

	existingCommands, err := s.ApplicationCommands(s.State.User.ID, "")
	if err != nil {
		log.Printf("Error fetching existing commands: %v", err)
		return
	}

	// Create maps for easier comparison
	existingMap := make(map[string]*discordgo.ApplicationCommand)
	for _, cmd := range existingCommands {
		existingMap[cmd.Name] = cmd
	}

	desiredMap := make(map[string]*discordgo.ApplicationCommand)
	for _, cmd := range commands {
		desiredMap[cmd.Name] = cmd
	}

	// Delete commands that no longer exist
	for name, existingCmd := range existingMap {
		if _, exists := desiredMap[name]; !exists {
			log.Printf("Deleting removed command: %s", name)
			err := s.ApplicationCommandDelete(s.State.User.ID, "", existingCmd.ID)
			if err != nil {
				log.Printf("Error deleting command %s: %v", name, err)
			}
		}
	}

	// Update or create commands
	for _, desiredCmd := range commands {
		if existingCmd, exists := existingMap[desiredCmd.Name]; exists {
			// Check if command needs updating (simple comparison)
			if !commandsEqual(existingCmd, desiredCmd) {
				log.Printf("Updating command: %s", desiredCmd.Name)
				_, err := s.ApplicationCommandEdit(s.State.User.ID, "", existingCmd.ID, desiredCmd)
				if err != nil {
					log.Printf("Error updating command %s: %v", desiredCmd.Name, err)
				}
			} else {
				log.Printf("Command %s is up to date", desiredCmd.Name)
			}
		} else {
			log.Printf("Creating new command: %s", desiredCmd.Name)
			_, err := s.ApplicationCommandCreate(s.State.User.ID, "", desiredCmd)
			if err != nil {
				log.Printf("Error creating command %s: %v", desiredCmd.Name, err)
			}
		}
	}

	log.Println("Command registration completed")
}

// commandsEqual performs a basic comparison between two commands
func commandsEqual(existing, desired *discordgo.ApplicationCommand) bool {
	if existing.Name != desired.Name ||
		existing.Description != desired.Description ||
		len(existing.Options) != len(desired.Options) {
		return false
	}

	// Compare options (basic comparison)
	for i, existingOpt := range existing.Options {
		if i >= len(desired.Options) {
			return false
		}
		desiredOpt := desired.Options[i]
		if existingOpt.Name != desiredOpt.Name ||
			existingOpt.Description != desiredOpt.Description ||
			existingOpt.Type != desiredOpt.Type ||
			existingOpt.Required != desiredOpt.Required {
			return false
		}
	}

	return true
}

// initCommands initializes command definitions with configuration
func initCommands(config *lib.Config) {
	commands = []*discordgo.ApplicationCommand{
		{
			Name:        "ping",
			Description: "ping pong!",
		},
		{
			Name:        "hs",
			Description: "This command was your nickname in highschool!",
			Options: []*discordgo.ApplicationCommandOption{
				{
					Type:        discordgo.ApplicationCommandOptionString,
					Name:        "nickname",
					Description: "Your nickname in highschool.",
					Required:    true,
				},
			},
		},
		{
			Name:        "bard",
			Description: "Ask the bard a question",
			Options: []*discordgo.ApplicationCommandOption{
				{
					Type:        discordgo.ApplicationCommandOptionString,
					Name:        "question",
					Description: "The question you want to ask",
					Required:    false,
				},
			},
		},
		{
			Name:        "ask",
			Description: "Ask a question and get a markov chain answer based on channel contents (like ChatGPT but dumb!)",
			Options: []*discordgo.ApplicationCommandOption{
				{
					Type:        discordgo.ApplicationCommandOptionString,
					Name:        "question",
					Description: "The question you want to ask",
					Required:    true,
				},
				{
					Type:        discordgo.ApplicationCommandOptionInteger,
					Name:        "messages",
					Description: fmt.Sprintf("Number of messages to use (default: %d, max: %d)", config.MarkovDefaultMessages, config.MarkovMaxMessages),
					Required:    false,
				},
			},
		},
		{
			Name:        "himbucks",
			Description: "Check your himbucks balance",
		},
		{
			Name:        "himboard",
			Description: "View the himbucks leaderboard",
		},
		{
			Name:        "sendbucks",
			Description: "Send himbucks to another user",
			Options: []*discordgo.ApplicationCommandOption{
				{
					Type:        discordgo.ApplicationCommandOptionUser,
					Name:        "user",
					Description: "The user to send himbucks to",
					Required:    true,
				},
				{
					Type:        discordgo.ApplicationCommandOptionInteger,
					Name:        "amount",
					Description: "Amount of himbucks to send",
					Required:    true,
					MinValue:    &[]float64{1}[0],
				},
			},
		},
	}
}

// initCommandHandlers initializes command handlers with configuration
func initCommandHandlers(config *lib.Config) {
	commandHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
		"ping":     lib.HandleCommand("ping", time.Duration(config.PingCooldown)*time.Second, command.PingCommand),
		"hs":       lib.HandleCommand("hs", time.Duration(config.HsCooldown)*time.Second, command.HsCommand),
		"bard":     lib.HandleCommand("bard", time.Duration(config.MarkovCooldown)*time.Second, command.BardCommand),
		"ask":      lib.HandleCommand("ask", time.Duration(config.MarkovAskCooldown)*time.Second, command.MarkovQuestionCommand),
		"himbucks": lib.HandleCommand("himbucks", time.Duration(config.HimbucksCooldown)*time.Second, command.BalanceGetCommand),
		"himboard": lib.HandleCommand("himboard", time.Duration(config.HimboardCooldown)*time.Second, command.LeaderboardCommand),
		"sendbucks": lib.HandleCommand("sendbucks", time.Duration(config.SendbucksCooldown)*time.Second, command.BalanceSendCommand),
	}
}
