package action

import (
	"context"
	"fmt"
	"strings"

	"gorm.io/gorm"

	eutil "jiraport/pkg/errors"
	"jiraport/pkg/log"
	eng "jiraport/resolver/engineer"
	"jiraport/resolver/handler/action/receiver"
	"jiraport/resolver/handler/action/render"
	"jiraport/service/jira"
	"jiraport/service/lark"
	ntf "jiraport/service/notification"
)

type NotifyCommand struct {
	policyDB          *gorm.DB
	lark              *lark.Lark
	jira              *jira.Jira
	ns                *ntf.Notifications
	r                 *render.Render
	receiverSelectors []receiver.ReceiverSelector
}

func NewNotifyCommand(
	policyDB *gorm.DB,
	lark *lark.Lark,
	jira *jira.Jira,
	ns *ntf.Notifications) (*NotifyCommand, error) {

	r, err := render.New()
	if err != nil {
		return nil, err
	}

	staticReceiverSelector, err := receiver.NewStaticReceiverSelector()
	if err != nil {
		return nil, err
	}
	assigneeReceiverSelector, err := receiver.NewAssigneeReceiverSelector(policyDB)
	if err != nil {
		return nil, err
	}
	firemasterReceiverSelector, err := receiver.NewFiremasterReceiverSelector(policyDB)
	if err != nil {
		return nil, err
	}
	regionccReceiverSelector, err := receiver.NewRegionCCReceiverSelector(policyDB)
	if err != nil {
		return nil, err
	}

	receiverSelectors := []receiver.ReceiverSelector{
		staticReceiverSelector,
		assigneeReceiverSelector,
		firemasterReceiverSelector,
		regionccReceiverSelector,
	}

	return &NotifyCommand{policyDB: policyDB,
		lark:              lark,
		jira:              jira,
		ns:                ns,
		r:                 r,
		receiverSelectors: receiverSelectors,
	}, nil
}

func (cmd *NotifyCommand) selectReceiver(name string, issue *jira.Issue) ([]receiver.Receiver, error) {
	allReceivers := make([]receiver.Receiver, 0)
	for _, receiverSelector := range cmd.receiverSelectors {
		receivers, err := receiverSelector.SelectReceiver(name, issue)
		if err != nil {
			log.Error("msg", "select receiver error in selector", "name", name, "err", err)
		} else if len(receivers) != 0 {
			allReceivers = append(allReceivers, receivers...)
		}
	}
	return allReceivers, nil
}

func newMessage(method string, title string, text string, color string, re receiver.Receiver) (*lark.Message, error) {
	isUrgent := method != "card"
	var urgentType string
	if isUrgent {
		switch method {
		case "urgent":
			urgentType = "in-app"
		case "sms":
			urgentType = "sms"
		case "phone":
			urgentType = "phone"
		default:
			return nil, fmt.Errorf("method is %v, no valid urgent method found, accept \"urgent\", \"sms\", \"phone\"", method)
		}
	}

	msg := lark.Message{
		MessageType: "card",
		Title:       title,
		Text:        text,
		Color:       color,
		IsUrgent:    isUrgent,
		UrgentType:  urgentType,
	}

	switch re.Type() {
	case receiver.PersonReceiverType:
		msg.RawMode = true
		msg.ReceiverId = re.Id()
	case receiver.GroupReceiverType:
		msg.RawMode = false
		msg.ChatTag = re.Id()
		if msg.IsUrgent {
			msg.IsUrgent = false
			msg.UrgentType = ""
			log.Error("msg", "currently can not send urgent message to group, fallback to \"card\"")
		}
	default:
		return nil, fmt.Errorf("unsupported receiver type %s", re.Type())
	}
	return &msg, nil
}

func (cmd *NotifyCommand) send(method string, title string, text string, color string, re receiver.Receiver) (*lark.MessageIndex, error) {
	msg, err := newMessage(method, title, text, color, re)
	if err != nil {
		return nil, fmt.Errorf("create message error: %w", err)
	}
	log.Debug("msg", "send msg", "msg", msg)
	msgIndex, err := cmd.lark.Notify(msg)
	if err != nil {
		log.Error("msg", "notify error", "err", err, "sent", msg)
		return nil, fmt.Errorf("notify error: %w", err)
	}
	return msgIndex, nil
}

// notify person or group
func (cmd *NotifyCommand) Exec(ctx context.Context, actionExecInfo *ntf.NotificationActionExecInfo, args ...string) (bool, error) {
	// default value of method and receiverType
	method := "card"
	receiverType := "assignee"
	templateName := "default"
	logOnComment := false

	// get positional parameters
	if len(args) > 0 {
		method = args[0]
	}
	if len(args) > 1 {
		receiverType = args[1]
	}
	if len(args) > 2 {
		templateName = args[2]
	}
	if len(args) > 3 {
		logOnComment = args[3] == "LogOnComment"
	}

	nf := actionExecInfo.Notification

	receivers, err := cmd.selectReceiver(receiverType, nf.Issue)
	if err != nil {
		log.Error("msg", "select receiver error", "err", err)
		return false, err
	}

	if len(receivers) == 0 {
		return false, fmt.Errorf("no receiver found for receiverType of %s", receiverType)
	}

	log.Debug("msg", "get receivers", "receivers", receivers)

	// data pack will be passed to render
	data := make(map[string]interface{})
	data["Issue"] = nf.Issue

	// render message content
	title, text, color, err := cmd.r.Render(templateName, data)
	if err != nil {
		return false, fmt.Errorf("render error, %w", err)
	}

	// errList for error log
	errList := make([]error, 0)
	// okList for log on comment
	okList := make([]receiver.Receiver, 0)
	for _, re := range receivers {
		msgIndex, err := cmd.send(method, title, text, color, re)
		if err != nil {
			errList = append(errList, err)
			continue
		}
		okList = append(okList, re)

		err = cmd.ns.AddMessage(nf.ID, msgIndex)
		if err != nil {
			log.Error("msg", "add message error", "err", err, "notificationID", nf.ID, "messageIndex", msgIndex)
			errList = append(errList, fmt.Errorf("add message error for notification %d: %w", nf.ID, err))
		}
	}

	if logOnComment {
		personList := make([]string, 0)
		for _, re := range okList {
			if person, ok := re.(*eng.Engineer); ok {
				personList = append(personList, fmt.Sprintf("%s(%s)", person.Name, person.Email))
			}
		}
		if len(personList) > 0 {
			logComment := fmt.Sprintf("notified %s by lark", strings.Join(personList, ", "))
			if method == "sms" || method == "phone" {
				logComment = fmt.Sprintf("%s and %s", logComment, method)
			}
			err = cmd.jira.AddComment(nf.IssueKey, logComment)
			if err != nil {
				return false, err
			}
		}
	}
	if len(errList) != 0 {
		return false, eutil.IntoErrorList(errList...)
	}
	return true, nil
}
