package qos

import (
	"errors"
	"fmt"
	"math"

	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard/limits"
)

var errWeightsMustBeSpecified = errors.New("invalid weights: weights must be specified for all tags or not specified for any")

type tagConfig struct {
	Shares, Limit, Reserved *float64
}

func validateConfig(c *limits.Config) error {
	if c.MaxReadRunningOps() <= 0 {
		return fmt.Errorf("invalid 'max_read_running_ops = %d': must be greater than zero", c.MaxReadRunningOps())
	}
	if c.MaxReadWaitingOps() <= 0 {
		return fmt.Errorf("invalid 'max_read_waiting_ops = %d': must be greater than zero", c.MaxReadWaitingOps())
	}
	if c.MaxWriteRunningOps() <= 0 {
		return fmt.Errorf("invalid 'max_write_running_ops = %d': must be greater than zero", c.MaxWriteRunningOps())
	}
	if c.MaxWriteWaitingOps() <= 0 {
		return fmt.Errorf("invalid 'max_write_waiting_ops = %d': must be greater than zero", c.MaxWriteWaitingOps())
	}
	if err := validateTags(c.ReadTags()); err != nil {
		return fmt.Errorf("'read' config validation error: %w", err)
	}
	if err := validateTags(c.WriteTags()); err != nil {
		return fmt.Errorf("'write' config validation error: %w", err)
	}
	return nil
}

func validateTags(configTags []limits.IOTagConfig) error {
	tags := map[IOTag]tagConfig{
		IOTagClient:     {},
		IOTagInternal:   {},
		IOTagBackground: {},
		IOTagWritecache: {},
		IOTagPolicer:    {},
	}
	for _, t := range configTags {
		tag, err := FromRawString(t.Tag)
		if err != nil {
			return fmt.Errorf("invalid tag %s: %w", t.Tag, err)
		}
		if _, ok := tags[tag]; !ok {
			return fmt.Errorf("tag %s is not configurable", t.Tag)
		}
		tags[tag] = tagConfig{
			Shares:   t.Weight,
			Limit:    t.LimitOps,
			Reserved: t.ReservedOps,
		}
	}
	idx := 0
	var shares float64
	for t, v := range tags {
		if idx == 0 {
			idx++
			shares = float64Value(v.Shares)
		} else if (shares != 0 && float64Value(v.Shares) == 0) || (shares == 0 && float64Value(v.Shares) != 0) {
			return errWeightsMustBeSpecified
		}
		if float64Value(v.Shares) < 0 || math.IsNaN(float64Value(v.Shares)) {
			return fmt.Errorf("invalid weight for tag %s: must be positive value", t.String())
		}
		if float64Value(v.Limit) < 0 || math.IsNaN(float64Value(v.Limit)) {
			return fmt.Errorf("invalid limit_ops for tag %s: must be positive value", t.String())
		}
		if float64Value(v.Reserved) < 0 || math.IsNaN(float64Value(v.Reserved)) {
			return fmt.Errorf("invalid limit_ops for tag %s: must be positive value", t.String())
		}
	}
	return nil
}

func float64Value(f *float64) float64 {
	if f == nil {
		return 0.0
	}
	return *f
}

func isNoop(c *limits.Config) bool {
	return c.MaxReadRunningOps() == limits.NoLimit &&
		c.MaxReadWaitingOps() == limits.NoLimit &&
		c.MaxWriteRunningOps() == limits.NoLimit &&
		c.MaxWriteWaitingOps() == limits.NoLimit &&
		len(c.ReadTags()) == 0 &&
		len(c.WriteTags()) == 0
}