// Package entity add by blacknc <blacknc@163.com> at 2023/09/14 16:04:29
package entity

import (
	"dataservices/commons/utils"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"github.com/tidbcloud/tiinf/errs"
)

type EndpointArgs struct {
	Name            string `json:"name"`
	Type            string `json:"type"`
	Required        int8   `json:"required"`
	Default         string `json:"default"`
	Description     string `json:"description"`
	Enum            string `json:"enum,omitempty"`
	ItemType        string `json:"item_type,omitempty"`
	IsPathParameter bool   `json:"is_path_parameter,omitempty"`
}

func (a EndpointArgs) Equal(b EndpointArgs) bool {
	return a == b
}

func (a *EndpointArgs) Parse(val any) (ArgValue, error) {
	var av ArgValue
	switch a.Type {
	case EndpointArgTypeString:
		av = &StringValue{argValue: argValue{a, val}}
	case EndpointArgTypeNumber:
		av = &NumberValue{argValue: argValue{a, val}}
	case EndpointArgTypeInteger:
		av = &IntegerValue{argValue: argValue{a, val}}
	case EndpointArgTypeBoolean:
		av = &BooleanValue{argValue: argValue{a, val}}
	case EndpointArgTypeArray:
		av = &ArrayValue{argValue: argValue{a, val}}
	default:
		return nil, newErrInvalidArgType(a.Name, a.Type)
	}
	if err := av.Parse(); err != nil {
		return nil, err
	}
	return av, nil
}

func newErrInvalidArgType(name, typ string) error {
	return errs.NewBadRequest(fmt.Sprintf("Invalid parameter type for `%s`:%v", name, typ), nil)
}
func newErrInvalidArgValue(name string, value any, err error) error {
	return errs.NewBadRequest(fmt.Sprintf("Invalid parameter value for `%s`:%v", name, value), err)
}
func newErrArgIsRequired(name string) error {
	return errs.NewBadRequest(fmt.Sprintf("The `%v` parameter is required", name), nil)
}
func newErrArgNotInEnum(name string, enum string) error {
	return errs.NewBadRequest(fmt.Sprintf("Invalid parameter value for `%s`, expected values:%s", name, enum), nil)
}

type ArgValue interface {
	Parse() error
	Type() string
	ItemType() string
	Value() any
	Values() []any
	Uint64() (uint64, error)
	String() string
}

type argValue struct {
	arg *EndpointArgs
	val any
}

func (v *argValue) Type() string {
	return v.arg.Type
}

func (v *argValue) ItemType() string {
	return v.arg.Type
}

func (v *argValue) Uint64() (uint64, error) {
	return 0, errs.NewBadRequest("Unsupported operation with argument type:"+v.arg.Type, nil)
}

func (v *argValue) String() string {
	return fmt.Sprint(v.val)
}

type StringValue struct {
	argValue
	value *string
}

func (v *StringValue) Parse() error {
	if v.value != nil {
		return nil
	}

	var value string
	switch rval := v.val.(type) {
	case string:
		value = rval
	case []string:
		if len(rval) > 0 {
			value = rval[0]
		}
	case float64:
		value = fmt.Sprint(rval)
	case nil:
		// nothing to do
	default:
		return newErrInvalidArgValue(v.arg.Name, v.val, nil)
	}

	if value == "" {
		if v.arg.Required == 1 {
			return newErrArgIsRequired(v.arg.Name)
		}
		value = v.arg.Default
	}

	if len(v.arg.Enum) > 0 {
		enum := strings.Split(v.arg.Enum, ",")
		if !utils.ArrayIn(value, enum) {
			return newErrArgNotInEnum(v.arg.Name, v.arg.Enum)
		}
	}

	v.value = &value
	return nil
}

func (v *StringValue) Value() any {
	v.Parse()
	return *v.value
}

func (v *StringValue) Values() []any {
	v.Parse()
	return []any{*v.value}
}

func (v *StringValue) Uint64() (uint64, error) {
	res, err := strconv.ParseUint(*v.value, 10, 64)
	if err != nil {
		return 0, errs.NewBadRequest("Failed to convert argument to uint64 with type:"+v.arg.Type, err)
	}
	return res, nil
}

func (v *StringValue) String() string {
	return *v.value
}

type NumberValue struct {
	argValue
	value *float64
}

func (v *NumberValue) Type() string {
	return v.arg.Type
}

func (v *NumberValue) Parse() error {
	if v.value != nil {
		return nil
	}

	var (
		value    *float64
		valueStr string
	)

	switch rval := v.val.(type) {
	case string:
		if rval != "" {
			val, err := strconv.ParseFloat(rval, 64)
			if err != nil {
				return newErrInvalidArgValue(v.arg.Name, v.val, err)
			}
			value = &val
			valueStr = rval
		}
	case []string:
		if len(rval) > 0 && rval[0] != "" {
			val, err := strconv.ParseFloat(rval[0], 64)
			if err != nil {
				return newErrInvalidArgValue(v.arg.Name, v.val, err)
			}
			value = &val
			valueStr = rval[0]
		}
	case float64:
		value = &rval
		valueStr = fmt.Sprint(rval)
	case nil:
		// nothing to do
	default:
		return newErrInvalidArgValue(v.arg.Name, v.val, nil)
	}

	if value == nil {
		if v.arg.Required == 1 {
			return newErrArgIsRequired(v.arg.Name)
		}
		if v.arg.Default != "" {
			v.val = v.arg.Default
			return v.Parse()
		}
		var val float64 = 0
		value = &val
		valueStr = "0"
	}

	if len(v.arg.Enum) > 0 {
		enum := strings.Split(v.arg.Enum, ",")
		if !utils.IsSliceContain(enum, valueStr) {
			return newErrArgNotInEnum(v.arg.Name, v.arg.Enum)
		}
	}

	v.value = value
	return nil
}

func (v *NumberValue) Value() any {
	v.Parse()
	return *v.value
}

func (v *NumberValue) Values() []any {
	v.Parse()
	return []any{*v.value}
}

type IntegerValue struct {
	argValue
	value *int64
}

func (v *IntegerValue) Parse() error {
	if v.value != nil {
		return nil
	}

	var value *int64
	switch rval := v.val.(type) {
	case string:
		if rval != "" {
			val, err := strconv.ParseInt(rval, 10, 64)
			if err != nil {
				return newErrInvalidArgValue(v.arg.Name, v.val, err)
			}
			value = &val
		}
	case []string:
		if len(rval) > 0 && rval[0] != "" {
			val, err := strconv.ParseInt(rval[0], 10, 64)
			if err != nil {
				return newErrInvalidArgValue(v.arg.Name, v.val, err)
			}
			value = &val
		}
	case float64:
		rvalInt := int64(rval)
		value = &rvalInt
	case int64:
		value = &rval
	case nil:
		// nothing to do
	default:
		return newErrInvalidArgValue(v.arg.Name, v.val, nil)
	}

	if value == nil {
		if v.arg.Required == 1 {
			return newErrArgIsRequired(v.arg.Name)
		}
		if v.arg.Default != "" {
			v.val = v.arg.Default
			return v.Parse()
		}
		var val int64
		value = &val
	}

	if len(v.arg.Enum) > 0 {
		enum := strings.Split(v.arg.Enum, ",")
		valueStr := strconv.FormatInt(*value, 10)
		if !utils.IsSliceContain(enum, valueStr) {
			return newErrArgNotInEnum(v.arg.Name, v.arg.Enum)
		}
	}

	v.value = value
	return nil
}

func (v *IntegerValue) Value() any {
	v.Parse()
	return *v.value
}

func (v *IntegerValue) Values() []any {
	v.Parse()
	return []any{*v.value}
}

type BooleanValue struct {
	argValue
	value *bool
}

func (v *BooleanValue) Parse() error {
	if v.value != nil {
		return nil
	}

	var value *bool
	switch rval := v.val.(type) {
	case string:
		if rval != "" {
			val, err := strconv.ParseBool(rval)
			if err != nil {
				return newErrInvalidArgValue(v.arg.Name, v.val, err)
			}
			value = &val
		}
	case []string:
		if len(rval) > 0 && rval[0] != "" {
			val, err := strconv.ParseBool(rval[0])
			if err != nil {
				return newErrInvalidArgValue(v.arg.Name, v.val, err)
			}
			value = &val
		}
	case float64:
		rvalBool := rval == 1
		value = &rvalBool
	case nil:
		// nothing to do
	default:
		return newErrInvalidArgValue(v.arg.Name, v.val, nil)
	}

	if value == nil {
		if v.arg.Required == 1 {
			return newErrArgIsRequired(v.arg.Name)
		}
		if v.arg.Default != "" {
			v.val = v.arg.Default
			return v.Parse()
		}
		var val bool
		value = &val
	}

	v.value = value
	return nil
}

func (v *BooleanValue) Value() any {
	v.Parse()
	return *v.value
}

func (v *BooleanValue) Values() []any {
	v.Parse()
	return []any{*v.value}
}

type ArrayValue struct {
	argValue
	value []any
}

func (v *ArrayValue) Parse() error {
	if v.value != nil {
		return nil
	}

	var values []string
	switch rval := v.val.(type) {
	case string:
		if rval != "" {
			values = strings.Split(rval, ",")
		}
	case []string:
		if len(rval) > 0 && rval[0] != "" {
			values = strings.Split(rval[0], ",")
		}
	case nil:
		// nothing to do
	default:
		return newErrInvalidArgValue(v.arg.Name, v.val, nil)
	}

	if len(values) == 0 {
		if v.arg.Required == 1 {
			return newErrArgIsRequired(v.arg.Name)
		}
		if v.arg.Default != "" {
			v.val = v.arg.Default
			return v.Parse()
		}
		return newErrInvalidArgValue(v.arg.Name, nil, nil)
	}

	itemArg := &EndpointArgs{}
	itemArg.Name = v.arg.Name
	itemArg.Type = v.arg.ItemType
	itemArg.Enum = v.arg.Enum
	itemArg.Description = v.arg.Description
	for i := range values {
		av, err := itemArg.Parse(values[i])
		if err != nil {
			return err
		}
		v.value = append(v.value, av.Value())
	}

	return nil
}

func (v *ArrayValue) Value() any {
	v.Parse()
	return v.value
}

func (v *ArrayValue) Values() []any {
	v.Parse()
	return v.value
}

func (v *ArrayValue) ItemType() string {
	return v.arg.ItemType
}

type SqlArgValue struct {
	Type string
	Val  any
}

func (v *SqlArgValue) Value() (any, error) {
	switch v.Type {
	case EndpointArgTypeInteger:
		return v.Val.(json.Number).Int64()
	case EndpointArgTypeNumber:
		num := v.Val.(json.Number)
		if strings.Contains(num.String(), ".") {
			return v.Val.(json.Number).Float64()
		} else {
			return v.Val.(json.Number).Int64()
		}
	default:
		return v.Val, nil
	}
}

type EndpointArgsList []EndpointArgs

func (a EndpointArgsList) Equal(b EndpointArgsList) bool {
	if len(a) != len(b) {
		return false
	}

	aMap := make(map[string]EndpointArgs, len(a))
	bMap := make(map[string]EndpointArgs, len(b))
	nameMap := make(map[string]bool, len(a))
	for i := range a {
		aMap[a[i].Name] = a[i]
		nameMap[a[i].Name] = true
	}
	for i := range b {
		bMap[b[i].Name] = b[i]
		nameMap[b[i].Name] = true
	}
	for name := range nameMap {
		if !aMap[name].Equal(bMap[name]) {
			return false
		}
	}
	return true
}
