package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"io"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"

	"github.com/bwmarrin/discordgo"
	"gopkg.in/yaml.v3"
)

const API string = "https://steam.csgohub.xyz/"

var BotID string

var s *discordgo.Session

type YAMLFile struct {
	Config Config `yaml:"config"`
}

type Config struct {
	DISCORD_TOKEN string `yaml:"DISCORD_TOKEN"`
}

func ReadConfig() (*Config, error) {
	config := &YAMLFile{}
	cfgFile, err := os.ReadFile("./config.yaml")
	if err != nil {
		return nil, err
	}
	err = yaml.Unmarshal(cfgFile, config)
	return &config.Config, err
}

var (
	GuildID        = flag.String("guild", "", "Test guild ID. If not passed - bot registers commands globally")
	RemoveCommands = flag.Bool("rmcmd", true, "Remove all commands after shutdowning or not")
)

func init() {
	config, err := ReadConfig()
	if err != nil {
		panic(err)
	}
	s, err = discordgo.New("Bot " + config.DISCORD_TOKEN)
	if err != nil {
		log.Fatalf("Invalid bot parameters: %v", err)
	}
	u, err := s.User("@me")
	if err != nil {
		log.Fatal(err)
		return
	}

	BotID = u.ID
}

var (
	commands = []*discordgo.ApplicationCommand{
		{
			Name:        "link-steam",
			Description: "Link your steam account",
			Type:        discordgo.ChatApplicationCommand,
		},
		{
			Name:        "show-team",
			Description: "Shows the CS:GO friend codes of the other users in VC",
			Type:        discordgo.ChatApplicationCommand,
		},
	}

	commandHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
		"link-steam": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
			if i.Member == nil {
				token := GetToken(i.User.ID)

				s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
					Type: discordgo.InteractionResponseChannelMessageWithSource,
					Data: &discordgo.InteractionResponseData{
						Content: "Your verification link:",
					},
				})
				s.ChannelMessageSend(i.ChannelID, API+token)
				return
			}
			sender := i.Member.User.ID
			token := GetToken(sender)
			userDM, err := s.UserChannelCreate(sender)
			if err != nil {
				log.Fatal(err)
				s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
					Type: discordgo.InteractionResponseChannelMessageWithSource,
					Data: &discordgo.InteractionResponseData{
						Content: "Couldn't send you verification link",
					},
				})
				return
			}
			s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
				Type: discordgo.InteractionResponseChannelMessageWithSource,
				Data: &discordgo.InteractionResponseData{
					Content: "A verification link has been send to your DM's",
				},
			})
			s.ChannelMessageSend(userDM.ID, API+token)

		},
		"show-team": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
			// Find the guild for that channel.
			g, err := s.State.Guild(i.GuildID)
			if err != nil {
				// Could not find guild.
				log.Println("couldn't find guild")
				return
			}

			var vcChannelId string
			var vcUsers []string

			// Look for the message sender in that guild's current voice states.
			for _, vs := range g.VoiceStates {
				if vs.UserID == i.Member.User.ID {
					vcChannelId = vs.ChannelID
				}
			}

			for _, vs := range g.VoiceStates {
				if vs.ChannelID == vcChannelId {
					vcUsers = append(vcUsers, vs.UserID)
				}
			}
			var returnString strings.Builder
			friends := GetFriendCodes(vcUsers)
			for _, friend := range friends {
				if friend.DiscordId != "" {
					returnString.WriteString("<@" + friend.DiscordId + ">: " + friend.FriendCode + "\n")
				}
			}
			if returnString.String() == "" {
				returnString.WriteString("No friend codes found")
			}

			s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
				Type: discordgo.InteractionResponseChannelMessageWithSource,
				Data: &discordgo.InteractionResponseData{
					Content: returnString.String(),
				},
			})
		},
	}
)

type userStruct struct {
	Id         int    `json:"id"`
	CreatedAt  string `json:"created_at"`
	DiscordId  string `json:"discord_id"`
	FriendCode string `json:"friend_code"`
}

func GetFriendCodes(users []string) []userStruct {
	var userArr []userStruct

	for _, u := range users {
		var us userStruct
		us.DiscordId = u
		userArr = append(userArr, us)
	}
	jsonReq, err := json.Marshal(userArr)
	if err != nil {
		log.Println(err)
		return userArr
	}
	userArr = nil

	resp, err := http.Post("http://localhost:8383/api/v1/users", "application/json; charset=utf-8", bytes.NewBuffer(jsonReq))
	if err != nil {
		log.Fatalln(err)
	}

	defer resp.Body.Close()
	bodyBytes, _ := io.ReadAll(resp.Body)
	json.Unmarshal(bodyBytes, &userArr)

	return userArr
}

func GetToken(discordId string) string {
	resp, err := http.Get("http://localhost:8383/api/v1/token/" + discordId)
	if err != nil {
		log.Println(err)
		return ""
	}
	defer resp.Body.Close()
	bodyBytes, _ := io.ReadAll(resp.Body)
	bodyString := strings.Replace(string(bodyBytes), "\"", "", -1)
	println(bodyString)
	return bodyString
}

func main() {
	s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) { log.Println("Bot is up!") })
	s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
		if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok {
			h(s, i)
		}
	})
	s.Identify.Intents = discordgo.IntentsGuildVoiceStates
	err := s.Open()
	if err != nil {
		log.Fatalf("Cannot open the session: %v", err)
	}
	defer s.Close()

	createdCommands, err := s.ApplicationCommandBulkOverwrite(s.State.User.ID, *GuildID, commands)

	if err != nil {
		log.Fatalf("Cannot register commands: %v", err)
	}

	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt)
	<-stop
	log.Println("Gracefully shutting down")

	if *RemoveCommands {
		for _, cmd := range createdCommands {
			err := s.ApplicationCommandDelete(s.State.User.ID, *GuildID, cmd.ID)
			if err != nil {
				log.Fatalf("Cannot delete %q command: %v", cmd.Name, err)
			}
		}
	}
}
