package action

import (
	"context"
	"fmt"
	"log/slog"
	"strings"

	"github.com/tidbcloud/jiraport/pkg/clients/clinic"
	"github.com/tidbcloud/jiraport/pkg/clients/engineer"
	"github.com/tidbcloud/jiraport/pkg/clients/lark"
	"github.com/tidbcloud/jiraport/pkg/clients/oncall"
	"github.com/tidbcloud/jiraport/pkg/db"
	"github.com/tidbcloud/jiraport/pkg/entity"
	eutil "github.com/tidbcloud/jiraport/pkg/errors"
	"github.com/tidbcloud/jiraport/pkg/resolver/handler/action/receiver"
	"github.com/tidbcloud/jiraport/pkg/resolver/handler/action/render"
)

type NotifyCommand struct {
	logger            *slog.Logger
	larkClient        lark.Interface
	dbClient          db.Interface
	render            *render.Render
	receiverSelectors []receiver.ReceiverSelector
}

func NewNotifyCommand(
	logger *slog.Logger,
	dbClient db.Interface,
	larkClient lark.Interface,
	engineerClient engineer.Interface,
	cliniccli clinic.Interface,
	oncallClient oncall.Interface,
) (*NotifyCommand, error) {
	r, err := render.New(cliniccli, engineerClient)
	if err != nil {
		return nil, err
	}

	staticReceiverSelector, err := receiver.NewStaticReceiverSelector()
	if err != nil {
		return nil, err
	}
	assigneeReceiverSelector, err := receiver.NewAssigneeReceiverSelector(engineerClient)
	if err != nil {
		return nil, err
	}
	firemasterReceiverSelector, err := receiver.NewFiremasterReceiverSelector(engineerClient, oncallClient)
	if err != nil {
		return nil, err
	}
	regionccReceiverSelector, err := receiver.NewRegionCCReceiverSelector(logger, cliniccli, engineerClient)
	if err != nil {
		return nil, err
	}
	secondaryReceiverSelector, err := receiver.NewSecondaryReceiverSelector(engineerClient)
	if err != nil {
		return nil, err
	}
	customReceiverSelector, err := receiver.NewCustomReceiverSelector(engineerClient)
	if err != nil {
		return nil, err
	}
	leaderReceiverSelector, err := receiver.NewLeaderReceiverSelector(engineerClient, oncallClient)
	if err != nil {
		return nil, err
	}

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

	return &NotifyCommand{
		logger:            logger,
		dbClient:          dbClient,
		larkClient:        larkClient,
		render:            r,
		receiverSelectors: receiverSelectors,
	}, nil
}

func (cmd *NotifyCommand) selectReceiver(receiverType string, issue *entity.Issue) ([]receiver.Receiver, error) {
	allReceivers := make([]receiver.Receiver, 0)
	for _, receiverSelector := range cmd.receiverSelectors {
		receivers, err := receiverSelector.SelectReceiver(receiverType, issue)
		if err != nil {
			cmd.logger.Error("select receiver error in selector",
				"receiverType", receiverType, "err", err, "issue", issue.Key)
		} 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,
	botType string,
) (*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,
		BotType:     botType,
	}

	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,
	botType string,
) (*entity.MessageIndex, error) {
	msg, err := newMessage(method, title, text, color, re, botType)
	if err != nil {
		return nil, fmt.Errorf("create message error: %w", err)
	}
	cmd.logger.Info("send message", "msg", msg)
	msgIndex, err := cmd.larkClient.Notify(msg)
	if err != nil {
		cmd.logger.Error("send message error", "err", err, "msg", msg)
		return nil, fmt.Errorf("notify error: %w", err)
	}
	larkMsgIndex := entity.MessageIndex(msgIndex)
	return &larkMsgIndex, nil
}

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

	// 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 {
		botTypeSpec := args[3]
		if botTypeSpec == "both" {
			botTypes = append(botTypes, "default")
			botTypes = append(botTypes, "new_firewatch")
		} else {
			botTypes = append(botTypes, botTypeSpec)
		}
	} else {
		botTypes = append(botTypes, "default")
	}

	nf := actionExecInfo.Notification

	receivers, err := cmd.selectReceiver(receiverType, nf.Issue)
	if err != nil {
		return false, "", err
	}
	if len(receivers) == 0 {
		return false, "", fmt.Errorf("no receiver found for receiverType of %s", receiverType)
	}

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

	// render message content
	title, text, color, err := cmd.render.Render(templateName, data)
	if err != nil {
		return false, "", fmt.Errorf("render error, err: %w, issue: %v, url: %s", err, nf.Issue, nf.Issue.URL)
	}

	// errList for error log
	errList := make([]error, 0)
	// okList for log on comment
	okList := make([]receiver.Receiver, 0)
	// okMap for recor the receiver and the index id.
	okMap := make(map[string]entity.MessageIndex)
	for _, botType := range botTypes {
		for _, rx := range receivers {
			var bType string
			if rx.BotType() != "" {
				bType = rx.BotType()
			} else {
				bType = botType
			}
			msgIndex, err := cmd.send(method, title, text, color, rx, bType)
			if err != nil {
				errList = append(errList, err)
				continue
			}
			okList = append(okList, rx)
			okMap[rx.ID()] = *msgIndex

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

	// now only log notification to person
	personList := make([]string, 0)
	for _, rx := range okList {
		if rx.Type() == receiver.PersonReceiverType {
			personList = append(personList, "("+rx.Show()+", "+string(okMap[rx.ID()])+")")
		}
	}
	var msg string
	if len(personList) > 0 {
		msg = fmt.Sprintf("notified %s by lark", strings.Join(personList, ", "))
		if method == "sms" || method == "phone" {
			msg = fmt.Sprintf("%s and %s", msg, method)
		}
	}

	if len(errList) != 0 {
		return false, msg, eutil.IntoErrorList(errList...)
	}
	return true, msg, nil
}
