package action

import (
	"context"
	"encoding/json"
	"errors"
	"log/slog"

	"github.com/tidbcloud/jiraport/pkg/clients/clinic"
	"github.com/tidbcloud/jiraport/pkg/clients/devops"
	"github.com/tidbcloud/jiraport/pkg/clients/engineer"
	"github.com/tidbcloud/jiraport/pkg/clients/jira"
	"github.com/tidbcloud/jiraport/pkg/clients/lambda"
	"github.com/tidbcloud/jiraport/pkg/clients/lark"
	"github.com/tidbcloud/jiraport/pkg/clients/oncall"
	"github.com/tidbcloud/jiraport/pkg/config"
	"github.com/tidbcloud/jiraport/pkg/db"
	"github.com/tidbcloud/jiraport/pkg/entity"
	ntf "github.com/tidbcloud/jiraport/pkg/resolver/notification"
)

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

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

type NotificationActionHandler struct {
	logger          *slog.Logger
	commands        map[string]Command
	dbClient        db.Interface
	jiraClient      jira.Interface
	notificationSvc *ntf.Notifications
}

func New(
	logger *slog.Logger,
	dbClient db.Interface,
	clinicClient clinic.Interface,
	jiraClient jira.Interface,
	larkClient lark.Interface,
	engineerClient engineer.Interface,
	devopsClient devops.Interface,
	oncallClient oncall.Interface,
	notificationSvc *ntf.Notifications,
) (*NotificationActionHandler, error) {
	commands := make(map[string]Command)
	notifyCommand, err := NewNotifyCommand(logger, dbClient, larkClient, engineerClient, clinicClient, oncallClient)
	if err != nil {
		return nil, err
	}
	commands["notify"] = notifyCommand

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

	assignCommand, err := NewAssignCommand(logger, jiraClient, engineerClient, notificationSvc, oncallClient, dbClient)
	if err != nil {
		return nil, err
	}
	commands["assign"] = assignCommand

	fillFBOwnerCommand, err := NewFillFBOwnerCommand(logger, jiraClient, larkClient, clinicClient, engineerClient)
	if err != nil {
		return nil, err
	}
	commands["fillfbowner"] = fillFBOwnerCommand

	escalateCommand, err := NewEscalateCommand(logger, jiraClient, engineerClient, oncallClient, notificationSvc)
	if err != nil {
		return nil, err
	}
	commands["escalate"] = escalateCommand

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

	evalCommand, err := NewEvalCommand(logger)
	if err != nil {
		return nil, err
	}
	commands["eval"] = evalCommand

	reportCommand, err := newReportCommand(clinicClient)
	if err != nil {
		return nil, err
	}
	commands["report"] = reportCommand

	backfillCommand, err := newBackfillCommand(logger, jiraClient, clinicClient, devopsClient)
	if err != nil {
		return nil, err
	}
	commands["backfill"] = backfillCommand

	guardianCommand, err := newGuardianCommand(devopsClient)
	if err != nil {
		return nil, err
	}
	commands["guardian"] = guardianCommand

	customCommand, err := NewCustomCommand(logger, oncallClient, jiraClient, dbClient)
	if err != nil {
		return nil, err
	}
	commands["custom"] = customCommand

	transitionCommand, err := newTransitionCommand(logger, jiraClient)
	if err != nil {
		return nil, err
	}
	commands["transition"] = transitionCommand

	httpForwardCommand, err := newHTTPForwardCommand(logger)
	if err != nil {
		return nil, err
	}
	commands["http_forward"] = httpForwardCommand

	return &NotificationActionHandler{
		logger:          logger,
		commands:        commands,
		dbClient:        dbClient,
		notificationSvc: notificationSvc,
		jiraClient:      jiraClient,
	}, nil
}

func (handler *NotificationActionHandler) Handle(ctx context.Context, ev lambda.Event) {
	var actionExecInfo entity.NotificationActionExecInfo
	err := json.Unmarshal(ev.Payload, &actionExecInfo)
	if err != nil {
		handler.logger.Error("decode action event failed", "err", err, "payload", ev.Payload)
		return
	}

	cmd := actionExecInfo.Action.Cmd
	handler.logger.Info("receive action event",
		"command", cmd,
		"issue", actionExecInfo.Notification.IssueKey,
		"policy", actionExecInfo.Notification.Policy,
		"notificationExecID", actionExecInfo.ID,
	)
	if len(cmd) == 0 {
		handler.logger.Info("no command to execute",
			"issue", actionExecInfo.Notification.IssueKey)
		return
	}

	// alertstore owns this alert's response. Stop the notification here rather
	// than firing on_success: the outward actions left in this policy are
	// suppressed too, so walking the state machine on would only re-invoke the
	// resolver — through the blocking timeout command — to suppress them one at
	// a time. The exec info is still closed out so nothing takes it for
	// unfinished work.
	if suppressedByHandover(actionExecInfo.Notification.Issue, cmd) {
		handler.logger.Info("alert handed over to alertstore, skipping action",
			"command", cmd,
			"issue", actionExecInfo.Notification.IssueKey,
			"policy", actionExecInfo.Notification.Policy,
			"notificationExecID", actionExecInfo.ID,
		)
		err := handler.dbClient.SetNotificationActionExecInfoFinished(actionExecInfo.ID, entity.ActionStatusSkipped)
		if err != nil {
			handler.logger.Error("finish skipped action error",
				"err", err,
				"issue", actionExecInfo.Notification.IssueKey,
				"notificationExecID", actionExecInfo.ID,
			)
		}
		return
	}

	cmdOk, cmdMsg, cmdErr := handler.executeAction(ctx, &actionExecInfo, cmd)
	if actionExecInfo.Action.LogOnComment && cmdMsg != "" {
		err := handler.jiraClient.AddComment(actionExecInfo.Notification.IssueKey, cmdMsg)
		if err != nil {
			handler.logger.Error("log on comment error",
				"err", err,
				"issue", actionExecInfo.Notification.IssueKey,
				"notificationExecID", actionExecInfo.ID)
		}
	}
	if cmdErr != nil {
		handler.logger.Error("execute action error",
			"err", cmdErr,
			"issue", actionExecInfo.Notification.IssueKey,
			"notificationExecID", actionExecInfo.ID,
		)
		if cmdErr == errContextDone {
			return
		}
	}

	var status uint
	var event string

	if cmdErr != nil {
		status = entity.ActionStatusError
		event = actionExecInfo.Action.OnError
	} else if cmdOk {
		status = entity.ActionStatusTrue
		event = actionExecInfo.Action.OnSuccess
	} else {
		status = entity.ActionStatusFalse
		event = actionExecInfo.Action.OnFailure
	}

	err = handler.dbClient.SetNotificationActionExecInfoFinished(actionExecInfo.ID, status)
	if err != nil {
		handler.logger.Error("finish action error", "err", err)
		return
	}

	transitionEvent := config.NotificationTransitionEvent(event)
	err = handler.notificationSvc.Transition(actionExecInfo.Notification.ID, transitionEvent)
	err = ntf.HandleTransitionError(err, func(te *db.TransitionError) error {
		handler.logger.Warn("do transition error", "err", te)
		return nil
	})
	if err != nil {
		handler.logger.Error("do transition error", "err", err)
	}
	handler.logger.Info("handle action event success",
		"command", cmd,
		"issue", actionExecInfo.Notification.IssueKey,
		"notificationExecID", actionExecInfo.ID)
}

func (handler *NotificationActionHandler) executeAction(ctx context.Context,
	actionExecInfo *entity.NotificationActionExecInfo,
	cmd []string) (bool, string, 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...)
}
