package discord

import (
	"fmt"
	"log"
	"os"
	"os/signal"
	"ticket-pimp/client/discord/discord_handler"
	"ticket-pimp/client/discord/discord_router"

	"ticket-pimp/internal/controller"
	"ticket-pimp/internal/domain"
	"ticket-pimp/internal/external"

	"github.com/bwmarrin/discordgo"
)

var (
	minLength   int    = 3
	repoType    string = "repo_type"
	projectRepo string = "project_repo"
	buildRepo   string = "build_repo"
	nameOption  string = "name"
	tagsPreset         = [3]discordgo.ForumTag{
		{
			Name:      "В работе",
			Moderated: true,
			EmojiName: "👩‍🍳",
		},
		{
			Name:      "Готово",
			Moderated: true,
			EmojiName: "✅",
		},
		{
			Name:      "Не начат",
			Moderated: true,
			EmojiName: "🚧",
		},
	}
	commands = []discordgo.ApplicationCommand{
		{
			Name:        "ping",
			Description: "pongs in a reply",
		},
		{
			Name:        "coda_ticket",
			Description: "Creates ticket in Coda.io w/ provided info",
		},
		{
			Name:        "init_project",
			Description: "Connect project with Coda ID",
			Options: []*discordgo.ApplicationCommandOption{
				{
					Type:        discordgo.ApplicationCommandOptionString,
					Name:        "key",
					Description: "Project's key from Coda.io",
					Required:    true,
					MinLength:   &minLength,
				},
			},
		},
		{
			Name:        "project",
			Description: "Create new development ticket",
			Options: []*discordgo.ApplicationCommandOption{
				{
					Type:        discordgo.ApplicationCommandOptionString,
					Name:        "project_name",
					Description: "Temporary project name",
					Required:    true,
					MinLength:   &minLength,
				},
			},
		},
		{
			Name:        "info",
			Description: "Get project's info",
		},
		{
			Name:        "repo",
			Description: "Creates repository of selected type. Name used for projects channels only",
			Options: []*discordgo.ApplicationCommandOption{
				{
					Type:        discordgo.ApplicationCommandOptionString,
					Name:        repoType,
					Description: "The type of repo",
					Required:    true,
					Choices: []*discordgo.ApplicationCommandOptionChoice{
						{
							Name:  "Unity project repo",
							Value: projectRepo,
						},
						{
							Name:  "XCode build repo",
							Value: buildRepo,
						},
					},
				},
				{
					Type:        discordgo.ApplicationCommandOptionString,
					Name:        nameOption,
					Description: "Type the repository's name",
					Required:    false,
					MinLength:   &minLength,
				},
			},
		},
		{
			Name:        "folder",
			Description: "Command for cloud folder creation",
			Options: []*discordgo.ApplicationCommandOption{
				{
					Type:        discordgo.ApplicationCommandOptionString,
					Name:        nameOption,
					Description: "Type the folder's name",
					Required:    false,
					MinLength:   &minLength,
				},
			},
		},
	}
)

func initBotWith(token string) *discordgo.Session {
	discord, err := discordgo.New("Bot " + token)
	if err != nil {
		log.Fatalf("unable to create discord session: %v", err)
	}

	return discord
}

type DiscordOptions struct {
	Config     *domain.Config
	Controller *controller.WorkflowController
}

func updateForum(conf *domain.Config, s *discordgo.Session) ([]discordgo.ForumTag, error) {

	log.Println("Updating forum chan...")

	// Get tasks channel instance:
	forum, err := s.Channel(conf.Discord.IsTaskForum)
	if err != nil {
		return nil, err
	}

	// Map all pre-set tags
	var tagsMap = map[string]discordgo.ForumTag{}
	for _, t := range forum.AvailableTags {
		tagsMap[t.Name] = t
	}

	// Result tags array
	tags := forum.AvailableTags

	// Check if preset tag exists into current channel..
	for i := 0; i < len(tagsPreset); i++ {
		_, ok := tagsMap[tagsPreset[i].Name]
		if !ok {
			// .. and append them if they aren't
			tags = append(tags, tagsPreset[i])
		}
	}

	dchan, err := s.ChannelEditComplex(forum.ID, &discordgo.ChannelEdit{
		AvailableTags: &tags,
	})
	if err != nil {
		return nil, err
	}

	log.Printf("Channel %s with ID %s propagated by tags:", dchan.Name, dchan.ID)
	for _, t := range dchan.AvailableTags {
		fmt.Printf("N: %s, ID: %s", t.Name, t.ID)
	}

	// Update config w/ tags:
	confTags := make(map[domain.TaskState]string)

	for _, tag := range dchan.AvailableTags {
		switch tag.Name {
		case "Не начат":
			confTags[domain.State(0)] = tag.ID
		case "В работе":
			confTags[domain.State(1)] = tag.ID
		case "Готово":
			confTags[domain.State(2)] = tag.ID
		}
	}

	conf.Discord.Tags = confTags

	return dchan.AvailableTags, nil
}

func commandRegistration(s *discordgo.Session, commands []discordgo.ApplicationCommand) []*discordgo.ApplicationCommand {
	log.Println("Adding commands...")
	var cmds []*discordgo.ApplicationCommand
	for _, cmd := range commands {
		cmd, err := s.ApplicationCommandCreate(s.State.User.ID, "", &cmd)
		if err != nil {
			log.Panicf("Cannot create '%v' command: %v", cmd.Name, err)
		}
		cmds = append(cmds, cmd)
		log.Println(cmd.Name + " command added")
	}
	return cmds
}

func Run(conf *domain.Config, opts DiscordOptions) error {

	// bot init
	s := initBotWith(conf.Discord.Token)

	// Init new handler
	h := discord_handler.New(
		opts.Controller,
		&conf.Discord,
		external.NewDummyClient(conf.Telegram),
	)

	r := discord_router.NewApp(s)

	var commonMw = []discord_router.Middleware{
		h.WithInitialResponse,
		h.RejectPM,
	}

	r.Use(commonMw...).
		Route("ping", h.Ping).
		Route("project", h.CreateProject).
		Route("info", h.ProjectInfo).
		Route("repo", h.CreateGit).
		Route("folder", h.CreateFolder).
		Route("init_project", h.InitChannelAsProject).
		Route("coda_ticket", h.CreateCoda)

	// and components
	r.
		/*Use().*/ // Combining into group duplicates replies
		Route("task_start", h.HandleTaskButtons).
		Route("task_close", h.HandleTaskButtons)

	// Add posts listener
	s.AddHandler(h.ListenPosts)

	// Add interactions handlers
	s.AddHandler(r.Serve)

	// session opening
	if err := s.Open(); err != nil {
		return fmt.Errorf("cannot open the session: %v", err)
	}

	// Forum update
	tags, err := updateForum(conf, s)
	if err != nil {
		log.Println(err.Error())
	}

	//Update handler with tags:
	h.SetAvailableTags(tags)

	// commands registration
	cmds := commandRegistration(s, commands)

	// gracefull shutdown
	defer s.Close()
	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt)
	<-stop
	log.Println("Graceful shutdown")

	log.Println("Removing commands...")
	for _, h := range cmds {
		err := s.ApplicationCommandDelete(s.State.User.ID, "", h.ID)
		if err != nil {
			log.Panicf("Cannot delete '%v' command: %v", h.Name, err)
		}
	}
	return nil
}
