package spu

import (
	"errors"
	"fmt"
	"strconv"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
)

type Labels map[globaldedicatedv1.Sku_Condition_Key]string

func (l Labels) Has(label globaldedicatedv1.Sku_Condition_Key) bool {
	_, ok := l[label]
	return ok
}

func (l Labels) Get(label globaldedicatedv1.Sku_Condition_Key) string {
	return l[label]
}

// Selector contains values, a key, and an operator that relates the key and values.
// The zero value of Selector is invalid.
// Selector implements both set based match and exact match
// Selector should be initialized via NewSelector constructor for creating a valid Selector.
type Selector struct {
	key      globaldedicatedv1.Sku_Condition_Key
	operator globaldedicatedv1.Sku_Condition_Operator
	values   []string
}

// NewSelector is the constructor for a Selector.
// If any of these rules is violated, an error is returned:
// (1) The operator can only be In, NotIn, Equals, NotEquals, or Range.
// (2) If the operator is In or NotIn, the values set must be non-empty.
// (3) If the operator is Equals, or NotEquals, the values set must contain one value.
// (5) If the operator is Range, the values set must contain only two values, which will be interpreted as integer.
func NewSelector(
	key globaldedicatedv1.Sku_Condition_Key,
	op globaldedicatedv1.Sku_Condition_Operator,
	values []string,
) (*Selector, error) {
	switch op {
	case globaldedicatedv1.Sku_Condition_IN, globaldedicatedv1.Sku_Condition_NOTIN:
		if len(values) == 0 {
			return nil, errors.New("for 'in', 'notin' operators, values set can't be empty")
		}
	case globaldedicatedv1.Sku_Condition_EQ, globaldedicatedv1.Sku_Condition_NE:
		if len(values) != 1 {
			return nil, errors.New("exact-match compatibility requires one single value")
		}
	case globaldedicatedv1.Sku_Condition_RANGE:
		if len(values) != 2 {
			return nil, errors.New("for 'range' operator, exactly two values are required")
		}

		start, err := strconv.ParseInt(values[0], 10, 64)
		if err != nil {
			return nil, fmt.Errorf("for 'range' operators, the value must be an integer: %w", err)
		}

		end, err := strconv.ParseInt(values[1], 10, 64)
		if err != nil {
			return nil, fmt.Errorf("for 'range' operators, the value must be an integer: %w", err)
		}

		if start > end {
			return nil, errors.New("for 'range' operators, the start value cannot be greater than the end value")
		}

	default:
		return nil, fmt.Errorf("unsupport operator %s", op)
	}

	return &Selector{
		key:      key,
		operator: op,
		values:   values,
	}, nil
}

// Matches returns true if the Selector matches the input Labels.
// There is a match in the following cases:
// (1) The operator is Equals and Labels has the Selector's key and Labels'
//
//	value for that key equals Selector's value.
//
// (2) The operator is NotEquals and Labels has the Selector's key and Labels'
//
//	value for that key not equals Selector's value.
//
// (3) The operator is In, Labels has the Selector's key and Labels'
//
//	value for that key is in Selector's value set.
//
// (4) The operator is NotIn, Labels has the Selector's key and
//
//	Labels' value for that key is not in Selector's value set.
//
// (5) The operator is Range, and Labels has
//
//	the Selector's key and the corresponding value is in the range.
func (s *Selector) Matches(l Labels) bool {
	switch s.operator {
	case globaldedicatedv1.Sku_Condition_EQ, globaldedicatedv1.Sku_Condition_IN:
		if !l.Has(s.key) {
			return false
		}

		return s.hasValue(l.Get(s.key))
	case globaldedicatedv1.Sku_Condition_NOTIN, globaldedicatedv1.Sku_Condition_NE:
		if !l.Has(s.key) {
			return false
		}

		return !s.hasValue(l.Get(s.key))
	case globaldedicatedv1.Sku_Condition_RANGE:
		if !l.Has(s.key) {
			return false
		}

		return s.inRange(l.Get(s.key))
	default:
		return false
	}
}

func (s *Selector) hasValue(value string) bool {
	for i := range s.values {
		if s.values[i] == "*" {
			return true
		}

		if s.values[i] == value {
			return true
		}
	}

	return false
}

func (s *Selector) inRange(value string) bool {
	v, err := strconv.ParseUint(value, 10, 64)
	if err != nil {
		tilog.PlainError("convert value to int failed", zap.String("value", value))
		return false
	}

	if len(s.values) != 2 {
		tilog.PlainError("invalid range values", zap.Any("key", s.key), zap.Strings("values", s.values))
		return false
	}

	start, err := strconv.ParseUint(s.values[0], 10, 64)
	if err != nil {
		tilog.PlainError("convert value to int failed", zap.String("value", s.values[0]))
		return false
	}

	end, err := strconv.ParseUint(s.values[1], 10, 64)
	if err != nil {
		tilog.PlainError("convert value to int failed", zap.String("value", s.values[1]))
		return false
	}

	return v >= start && v <= end
}
