package action

import (
	"context"
	"errors"

	"gorm.io/gorm"

	"jiraport/pkg/log"
	"jiraport/pkg/queue"
	"jiraport/service/jira"
	"jiraport/service/lark"
	ntf "jiraport/service/notification"
	"jiraport/service/timer"
)

const (
	StatusTrue  = 1
	StatusFalse = 2
	StatusError = 3
)

var contextDoneErr = errors.New("context is done, action return early")

type Command interface {
	Exec(ctx context.Context, actionExecInfo *ntf.NotificationActionExecInfo, args ...string) (bool, error)
}

type NotificationActionHandler struct {
	commands map[string]Command
	ns       *ntf.Notifications
}

func New(policyDB *gorm.DB,
	jira *jira.Jira,
	lark *lark.Lark,
	timer *timer.Timer,
	ns *ntf.Notifications) (*NotificationActionHandler, error) {

	commands := make(map[string]Command)

	notifyCommand, err := NewNotifyCommand(policyDB, lark, jira, ns)
	if err != nil {
		return nil, err
	}
	commands["notify"] = notifyCommand

	timeoutCommand, err := NewTimeoutCommand(timer)
	if err != nil {
		return nil, err
	}
	commands["timeout"] = timeoutCommand

	assignCommand, err := NewAssignCommand(policyDB, jira, ns)
	if err != nil {
		return nil, err
	}
	commands["assign"] = assignCommand

	escalateCommand, err := NewEscalateCommand(policyDB, jira, ns)
	if err != nil {
		return nil, err
	}
	commands["escalate"] = escalateCommand

	commentCommand, err := NewCommentCommand(jira)
	if err != nil {
		return nil, err
	}
	commands["comment"] = commentCommand

	return &NotificationActionHandler{commands: commands, ns: ns}, nil
}

func (handler *NotificationActionHandler) Handle(ctx context.Context, ev queue.Event) {
	log.Debug("handle", ev.Name)
	actionExecInfo, ok := ev.Payload.(*ntf.NotificationActionExecInfo)
	if !ok {
		log.Error("msg", "unexpected payload", "payload", ev.Payload)
		return
	}

	if actionExecInfo == nil {
		return
	}

	cmd := actionExecInfo.Action.Cmd
	if len(cmd) == 0 {
		return
	}

	cmdOk, cmdErr := handler.executeAction(ctx, actionExecInfo, cmd)
	if cmdErr != nil {
		log.Error("msg", "execut action error", "err", cmdErr)
		if cmdErr == contextDoneErr {
			return
		}
	}

	var status uint
	var event string

	if cmdErr != nil {
		status = StatusError
		event = actionExecInfo.Action.OnError
	} else if cmdOk {
		status = StatusTrue
		event = actionExecInfo.Action.OnSuccess
	} else {
		status = StatusFalse
		event = actionExecInfo.Action.OnFailure
	}

	err := handler.ns.SetFinished(actionExecInfo.ID, status)
	if err != nil {
		log.Error("msg", "finish action error", "err", err)
		return
	}

	if event != "" {
		transitionEvent := ntf.NotificationTransitionEvent(event)
		err = handler.ns.Transition(actionExecInfo.Notification.ID, transitionEvent)
		err = ntf.HandleTransitionError(err, func(te *ntf.TransitionError) error {
			log.Warn("msg", "do transition error", "err", te)
			return nil
		})
		if err != nil {
			log.Error("msg", "do transition error", "err", err)
		}
	}
}

func (handler *NotificationActionHandler) executeAction(ctx context.Context, actionExecInfo *ntf.NotificationActionExecInfo, cmd []string) (bool, error) {
	f, args := cmd[0], cmd[1:]
	cmdExecutor, ok := handler.commands[f]
	if !ok {
		return false, errors.New("no matched action function")
	}
	return cmdExecutor.Exec(ctx, actionExecInfo, args...)
}
