package discord_handler

import (
	"context"
	"fmt"
	"log"
	"ticket-pimp/adapters"
	router "ticket-pimp/client/discord/discord_router"
	"ticket-pimp/internal/controller"
	"ticket-pimp/internal/domain"
	"ticket-pimp/internal/helpers"

	"github.com/bwmarrin/discordgo"
)

type Handler struct {
	controller          *controller.WorkflowController
	conf                *domain.DiscordConfig
	telegramDummyClient adapters.IDummyTelegram
	tags                []discordgo.ForumTag
}

func New(
	controller *controller.WorkflowController,
	conf *domain.DiscordConfig,
	tg adapters.IDummyTelegram,
) *Handler {
	return &Handler{
		controller:          controller,
		conf:                conf,
		telegramDummyClient: tg,
	}
}

func (h *Handler) RejectPM(f router.HandlerFunc) router.HandlerFunc {
	return func(s *discordgo.Session, i *discordgo.InteractionCreate) {

		dchan, err := s.Channel(i.ChannelID)
		if err != nil {
			log.Println(err)
			return
		}

		if dchan.Type != discordgo.ChannelTypeDM {
			f(s, i)
			return
		}

		respondWithReject(s, i)
	}
}

func respondWithReject(s *discordgo.Session, i *discordgo.InteractionCreate) {

	var content = "Yo, fella! I'm not working in private!"

	err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
		Type: discordgo.InteractionResponseChannelMessageWithSource,
		Data: &discordgo.InteractionResponseData{
			Content: content,
		},
	})

	if err == nil {
		return
	}

	_, err = s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
		Content: &content,
	})

	if err != nil {
		log.Printf("unable to edit answer with unknown error %v", err)
	}
}

// Моментальный ответ для избежания столкновения с протуханием токена
func (h *Handler) WithInitialResponse(f router.HandlerFunc) router.HandlerFunc {
	return func(s *discordgo.Session, i *discordgo.InteractionCreate) {

		initialResponse := discordgo.InteractionResponse{
			Type: discordgo.InteractionResponseChannelMessageWithSource,
			Data: &discordgo.InteractionResponseData{
				Flags:   discordgo.MessageFlagsEphemeral,
				Content: "👩‍🍳 Cooking your query..",
			},
		}

		err := s.InteractionRespond(i.Interaction, &initialResponse)
		if err != nil {
			log.Println(err)
		}

		f(s, i)
	}
}

func (h *Handler) SetAvailableTags(tags []discordgo.ForumTag) {
	h.tags = append(h.tags, tags...)
}

func (h *Handler) Ping(s *discordgo.Session, i *discordgo.InteractionCreate) {

	var content string = fmt.Sprintf(
		"**Pong to:** %s\n**App ID:** %s\n**Guild ID:** %s\nC**hannelID:** %s",
		i.Member.User.Mention(),
		i.AppID,
		i.GuildID,
		i.ChannelID,
	)

	_, err := s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
		Content: &content,
	})

	if err != nil {
		log.Println(err)
	}
}

// ListenPosts
/*
	..listens to new posts in specific channel
 	to act them like a task
*/
func (h *Handler) ListenPosts(s *discordgo.Session, th *discordgo.ThreadCreate) {

	// Check if thread starter is not a bot, and thread started at the tasks channel;
	if th.ParentID != h.conf.IsTaskForum || th.OwnerID == s.State.User.ID {
		return
	}

	// Get all messages from the channel:
	msgs, _ := s.ChannelMessages(th.ID, 1, "", "", "")

	// Take the first one:
	msg, _ := s.ChannelMessage(th.ID, msgs[0].ID)

	if msg.Author.ID == s.State.User.ID {
		return
	}

	content := th.Name
	content += "\n" + msg.Content

	user, _ := s.GuildMember(th.GuildID, msg.Author.ID)

	t, err := h.controller.WriteTaskToDB(&domain.Task{
		Description: content,
		Creator:     user.User.Mention(),
	})
	if err != nil {
		s.ChannelMessageSend(th.ID, fmt.Sprintf("unable to write task to db, %v", err))
		return
	}

	// Отредактировать Thread name как для задачи
	appliedTags := []string{
		h.conf.Tags[domain.NewTaskState()],
		// h.tags[2].ID,
	}

	_, err = s.ChannelEditComplex(th.ID, &discordgo.ChannelEdit{
		Name:        fmt.Sprintf("Task ID: %d, by %s", t.ID, t.Creator),
		AppliedTags: &appliedTags,
	})
	if err != nil {
		log.Printf("th edition is not complete: %v", err)
	}

	// Fix the original task message:
	taskMessage, err := s.ChannelMessageSendComplex(th.ID, &discordgo.MessageSend{
		Content: content,
		Components: []discordgo.MessageComponent{
			discordgo.ActionsRow{
				Components: []discordgo.MessageComponent{
					discordgo.Button{
						Label:    "Start",
						Style:    discordgo.SuccessButton,
						Disabled: false,
						Emoji: discordgo.ComponentEmoji{
							Name:     "🚀",
							ID:       "",
							Animated: false,
						},
						URL:      "",
						CustomID: "task_start",
					},
					discordgo.Button{
						Label:    "Close",
						Style:    discordgo.DangerButton,
						Disabled: true,
						Emoji: discordgo.ComponentEmoji{
							Name:     "🍀",
							ID:       "",
							Animated: false,
						},
						URL:      "",
						CustomID: "task_close",
					},
				},
			},
		},
	})
	if err != nil {
		log.Printf("th start message edition is not complete: %v", err) // todo return error. Error causes panics into next method;
	}

	err = h.controller.UpdateTasksMessageID(context.TODO(), taskMessage.ID, t.ID)
	if err != nil {
		s.ChannelMessageSend(th.ID, fmt.Sprintf("unable to update task at the db, %v", err))
		return
	}
}

// handleTaskButtons
// .. handler function to work with the Action Buttons over a task
func (h *Handler) HandleTaskButtons(s *discordgo.Session, i *discordgo.InteractionCreate) {

	err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
		Type: discordgo.InteractionResponseChannelMessageWithSource,
		Data: &discordgo.InteractionResponseData{},
	})
	_ = err

	// Get assignee value; ---------------------------------------------------------------------------------
	user := i.Member.User.Mention()

	var (
		opt                  int              = -1
		doneButtonIsDisabled bool             = false
		state                domain.TaskState = domain.NewTaskState()
		message              string
		tag                  discordgo.ForumTag
	)

	// Check what flow was touched: -------------------------------------------------------------------------
	switch i.Interaction.MessageComponentData().CustomID {
	case "task_start":
		opt = 0
		doneButtonIsDisabled = false
		state = domain.InrpogressTaskState()
		message = "взята в работу"
		tag = h.tags[0]
	case "task_close":
		opt = 1
		doneButtonIsDisabled = true
		state = domain.DoneTaskState()
		message = "выполнена"
		tag = h.tags[1]
	}

	// Send the task update to db --------------------------------------------------------------------------
	convertable, err := h.controller.UpdateTask(i.Message.ID, opt, user)
	if err != nil {
		s.ChannelMessageSend(i.ChannelID, fmt.Sprintf("Unable to update task at the db w/ error: %v", err))
		return
	}

	// Map DB's response to domain.Task: -------------------------------------------------------------------
	task := convertable.
		ExtractDomain()

	newContent := task.DiscordMessage(state)

	// Send message to the creator in Telegram: -------------------------------------------------------------

	if task.CreatorLink != "" {
		h.telegramDummyClient.DummyNotification(
			task.CreatorLink,
			fmt.Sprintf("Task ID: %d %s", task.ID, message))
	}

	// Send a message to the thread about the task was started: ---------------------------------------------
	_, err = s.ChannelMessageSendComplex(i.ChannelID, &discordgo.MessageSend{
		Content: newContent,
	})
	if err != nil {
		log.Printf("error while sending start task message: %v", err)
	}

	// Fix the original task message: ----------------------------------------------------------------------
	_, err = s.ChannelMessageEditComplex(&discordgo.MessageEdit{
		Channel: i.ChannelID,
		ID:      i.Message.ID,
		Components: []discordgo.MessageComponent{
			discordgo.ActionsRow{
				Components: []discordgo.MessageComponent{
					discordgo.Button{
						Label:    "Close",
						Style:    discordgo.DangerButton,
						Disabled: doneButtonIsDisabled,
						Emoji: discordgo.ComponentEmoji{
							Name:     "🍀",
							ID:       "",
							Animated: false,
						},
						URL:      "",
						CustomID: "task_close",
					},
				},
			},
		},
	})
	if err != nil {
		log.Printf("th start message edition is not complete: %v", err)
	}

	err = h.setFlag(s, i, &tag)
	if err != nil {
		log.Printf("error while `start` tag setting: %v", err)
	}
}

// CreateFolder
/*
	- creates project's cloud folder;
	- writed folder link to db;
*/
func (h *Handler) CreateFolder(s *discordgo.Session, i *discordgo.InteractionCreate) {
	const (
		nameOption string = "folder_name"
	)

	// Моментальный ответ для избежания столкновения с протуханием токена
	initialResponse := discordgo.InteractionResponse{
		Type: discordgo.InteractionResponseChannelMessageWithSource,
		Data: &discordgo.InteractionResponseData{
			Flags:   discordgo.MessageFlagsEphemeral,
			Content: "👩‍🍳 Cooking your query..",
		},
	}

	s.InteractionRespond(i.Interaction, &initialResponse)

	// Определение переменной для ответа
	var result string = "unexpected result"

	// Определение выбранных вариантов ответа
	options := i.ApplicationCommandData().Options

	optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options))
	for _, opt := range options {
		optionMap[opt.Name] = opt
	}

	// Creating request:
	var req controller.FolderRequest
	name, insertedValueNotNil := optionMap[nameOption]
	dchan, err := s.Channel(i.ChannelID)

	if err != nil {
		log.Printf("error while identifying channel: %v", err)
	} else {

		if dchan.ParentID == h.conf.IsProjectChannel {
			req.ChannelID = dchan.ID
			if insertedValueNotNil {
				req.InsertedName = name.StringValue()
			}

		} else {
			req.ChannelID = ""
			if insertedValueNotNil {
				req.InsertedName = name.StringValue()
			}
		}
	}

	// Making request:
	resp := h.controller.CreateFolder(context.TODO(), req)
	if resp.Project == nil {
		result = "Надо написать имя для папки, или создать папку из проекта!"
	} else {
		result = resp.Project.DiscordString()
		if resp.Message != nil {
			result += "Errors: " + resp.Message.Error()
		}
	}

	h.defaultFollowUp(result, s, i)
}

// CreateGit
/*
	-creates project's git repository;
	- writed git link to db;
*/
func (h *Handler) CreateGit(s *discordgo.Session, i *discordgo.InteractionCreate) {

	const (
		typeOfRepo      = "repo_type"
		projectRepoType = "project_repo"
		buildRepoType   = "build_repo"
		nameOption      = "repo_name"
	)

	// Моментальный ответ для избежания столкновения с протуханием токена
	initialResponse := discordgo.InteractionResponse{
		Type: discordgo.InteractionResponseChannelMessageWithSource,
		Data: &discordgo.InteractionResponseData{
			Flags:   discordgo.MessageFlagsEphemeral,
			Content: "👩‍🍳 Cooking your query..",
		},
	}

	s.InteractionRespond(i.Interaction, &initialResponse)

	// Определение переменной для ответа
	var result string = "unexpected result"

	// Access options in the order provided by the user.
	options := i.ApplicationCommandData().Options

	// Or convert the slice into a map
	optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options))
	for _, opt := range options {
		optionMap[opt.Name] = opt
	}

	// Creating request:
	var req controller.GitRequest

	name, insertedValueNotNil := optionMap[nameOption]

	isBuild := optionMap[typeOfRepo]

	switch isBuild.StringValue() {
	case buildRepoType:
		req.IsBuildGit = true
	case projectRepoType:
		req.IsBuildGit = false
	}

	dchan, err := s.Channel(i.ChannelID)
	if err != nil {
		h.defaultFollowUp("error while identifying channel: %v", s, i)
		return
	} else {

		if dchan.ParentID == h.conf.IsProjectChannel {
			req.ChannelID = dchan.ID
			if insertedValueNotNil {
				req.InsertedName = name.StringValue()
			}
		} else {
			req.ChannelID = ""
			if insertedValueNotNil {
				req.InsertedName = name.StringValue()
			}
		}
	}

	// Making request:
	resp := h.controller.CreateGit(context.TODO(), req)
	if resp.Project == nil {
		if resp.Message != nil {
			result = resp.Message.Error()
		}
	} else {

		result = resp.Project.DiscordString()
		if resp.Message != nil {
			result += "Errors: " + resp.Message.Error()
		}

	}
	h.defaultFollowUp(result, s, i)
}

// ProjectInfo
/*
	Message in chat with related project information
*/
func (h *Handler) ProjectInfo(s *discordgo.Session, i *discordgo.InteractionCreate) {

	var result string

	// Get channel from the request
	dchan, err := s.Channel(i.ChannelID)
	if err != nil {
		result = "unable to get channel from the message"
	} else {
		project, err := h.controller.GetProjectByChannelID(context.TODO(), dchan.ID)
		if err != nil {
			result = err.Error()
		} else {
			if project != nil {
				result = project.DiscordString()
				if err != nil {
					result += "Errors: " + err.Error()
				}
			} else {
				result = "Something wrong with retrieving project from db"
			}

		}
	}

	h.defaultFollowUp(result, s, i)
}

// InitChannelAsProject
/*
	- makes channel-project raw in the db storage;
*/
func (h *Handler) InitChannelAsProject(s *discordgo.Session, i *discordgo.InteractionCreate) {

	var result string

	// Get channel from the request
	dchan, err := s.Channel(i.ChannelID)
	if err != nil {
		result = "unable to get channel from the message"
	} else {
		if dchan.ParentID != h.conf.IsProjectChannel {
			// Sending result:
			_, err := s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
				Content: "This channel is not at the project's group",
			})

			if err != nil {
				s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
					Content: fmt.Sprintf("Something went wrong: %v", err),
				})
				return
			}
			return
		}

		// Access options in the order provided by the user.
		options := i.ApplicationCommandData().Options

		// Or convert the slice into a map
		optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options))
		for _, opt := range options {
			optionMap[opt.Name] = opt
		}

		if option, ok := optionMap["key"]; ok {
			var errMsg error = nil

			project, err := h.controller.InitProjectInChannel(context.TODO(), i.ChannelID, option.StringValue())
			if err != nil {
				result = fmt.Sprintf("unable to init project: %v", err)
			} else {
				result = project.DiscordString()
				if errMsg != nil {
					result += "Errors: " + errMsg.Error()
				}
			}
		}
	}

	h.defaultFollowUp(result, s, i)
}

// CreateProject
/*
	- creates new proejct in the db;
	- creates new channel and writes it to db;
*/
func (h *Handler) CreateProject(s *discordgo.Session, i *discordgo.InteractionCreate) {

	var result string
	// Access options in the order provided by the user.
	options := i.ApplicationCommandData().Options

	// Or convert the slice into a map
	optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options))
	for _, opt := range options {
		optionMap[opt.Name] = opt
	}

	// Get Project's title from the request:
	var projectTitle string
	if option, ok := optionMap["project_name"]; !ok {
		return
	} else {
		projectTitle = option.StringValue()
	}

	// Create channel with specified title
	dchan, err := s.GuildChannelCreate(i.GuildID, projectTitle, discordgo.ChannelTypeGuildText)
	if err != nil {
		result = fmt.Sprintf("chan creation problem: %v\n", err)
		h.defaultFollowUp(result, s, i)
		return
	}

	// Create DB raw with new project:
	p, err := h.controller.ProjectCreate(context.TODO(), domain.Project{
		ChannelID: dchan.ID,
		Name:      projectTitle,
	})

	if err != nil {
		result = fmt.Sprintf("unable to create project: %v\n", err)

		// Revert channel creation:
		_, err := s.ChannelDelete(dchan.ID)
		if err != nil {
			result += fmt.Sprintf("\nunable to clean channel: %v\n", err)
		}

		h.defaultFollowUp(result, s, i)
		return
	}

	// Edit created channel:
	edit := discordgo.ChannelEdit{
		Name:     p.Key + "-" + helpers.Cut(projectTitle),
		ParentID: h.conf.IsProjectChannel,
	}

	dchan, err = s.ChannelEdit(dchan.ID, &edit)
	if err != nil {
		result = fmt.Sprintf("channel %s created, but unable to edit follow up message: %v\n", p.Key, err)
		h.defaultFollowUp(result, s, i)
		return

	}

	// Отправить сообщение о создании проекта:
	_, err = s.ChannelMessageSend(dchan.ID, p.DiscordString())
	if err != nil {
		log.Printf("message send problem: %v\n", err)
		result = "Project was created, but there is some problem with init channel message"
		h.defaultFollowUp(result, s, i)
	}
	result = fmt.Sprintf("Project was created: https://discord.com/channels/%s/%s", i.GuildID, dchan.ID)

	h.defaultFollowUp(result, s, i)
}

// CreateCoda
/*
	- sends request to Coda.io;
*/
func (h *Handler) CreateCoda(s *discordgo.Session, i *discordgo.InteractionCreate) {

	// Get channel from the request
	dchan, err := s.Channel(i.ChannelID)
	if err != nil {
		h.defaultFollowUp("unable to get channel from the message", s, i)
		return
	}

	if dchan.ParentID != h.conf.IsProjectChannel {
		h.defaultFollowUp("This channel is not at the project's group", s, i)
		return
	}

	//[ ] достать проект из базы и послать в коду
	result, err := h.controller.CreateCoda(i.GuildID, dchan.ID)
	if err != nil {
		h.defaultFollowUp(fmt.Sprintf("unable to create coda: %v", err), s, i)
		return
	}

	if err != nil {
		result += fmt.Sprintf("\nexecuted w/ error: %v", err)
	}

	h.defaultFollowUp(result, s, i)
}
