package plugin

import (
	"context"
	"errors"
	"strings"

	"github.com/go-logr/logr"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
)

type ActionState string

const (
	Success       ActionState = "Success"
	Error         ActionState = "Error"
	Unschedulable ActionState = "Unschedulable"
)

// ActionResult is the result of an plugin action
type ActionResult struct {
	result  ActionState
	err     error
	reasons []string
}

func NewActionResult(result ActionState, reasons ...string) *ActionResult {
	s := &ActionResult{
		result:  result,
		reasons: reasons,
	}
	if result == Error {
		s.err = errors.New(s.Message())
	}
	return s
}

func NewActionResultFromErr(err error) *ActionResult {
	return &ActionResult{
		result:  Error,
		err:     err,
		reasons: []string{err.Error()},
	}
}

// IsSuccess returns true if `result` is `Success`
func (s *ActionResult) IsSuccess() bool {
	return s.result == Success
}

// IsUnschedulable returns true if `result` is `Unschedulable`
func (s *ActionResult) IsUnschedulable() bool {
	state := s.result
	return state == Unschedulable
}

// IsUnschedulable returns true if `result` is `Error`
func (s *ActionResult) IsError() bool {
	state := s.result
	return state == Error
}

func (s *ActionResult) AsError() error {
	if s.IsSuccess() {
		return nil
	}
	if s.err != nil {
		return s.err
	}
	return errors.New(s.Message())
}

func (s *ActionResult) Message() string {
	if s == nil {
		return ""
	}
	return strings.Join(s.reasons, ", ")
}

type SchedContext struct {
	Ctx    context.Context
	Logger logr.Logger
	// State is used by plugins to store temporary data
	State map[string]any // key: plugin name
}

type AcctScore struct {
	AccountName string
	Score       int64
}

func NewSchedContext(ctx context.Context, logger logr.Logger) *SchedContext {
	return &SchedContext{
		Ctx:    ctx,
		Logger: logger,
		State:  make(map[string]any),
	}
}

type Plugin interface {
	Name() string
}

type FilterPlugin interface {
	Plugin

	PreFilter(ctx *SchedContext, network *cloudv1.Network, acct []*cloudv1.Account) *ActionResult
	// Filter is called to filter an account for a network.
	//
	// All plugins should return `Success` if the account fits the network, otherwise return `Unschedulable`.
	// If a plugin returns `Error`, the schedule process will interrupt.
	Filter(sctx *SchedContext, network *cloudv1.Network, acct *cloudv1.Account) *ActionResult
}

type ScorePlugin interface {
	Plugin

	// Score is called to score accounts for a network.
	// All plugins must return `Success` and rank for each account.
	Score(sctx *SchedContext, network *cloudv1.Network, accts []*cloudv1.Account) ([]AcctScore, *ActionResult)
}
