package validate

import (
	"encoding/xml"
	"errors"
	"fmt"
	"math"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"strconv"
	"strings"

	"golib/v1/features/mlib/mo"
)

type Field struct {
	Name          string   `xml:"Name,attr"`
	Type          mo.Type  `xml:"Type,attr"`
	Minimum       float64  `xml:"Minimum"`       // mo.TypeInt mo.TypeInt64 mo.TypeDouble mo.TypeDate mo.TypeDecimal128
	Maximum       float64  `xml:"Maximum"`       // mo.TypeInt mo.TypeInt64 mo.TypeDouble mo.TypeDate mo.TypeDecimal128
	Enums         []string `xml:"Enums>Enum"`    // All Type are officially supported, but only Number and String Type are supported here
	MinLength     uint64   `xml:"MinLength"`     // mo.TypeString
	MaxLength     uint64   `xml:"MaxLength"`     // mo.TypeString
	Pattern       string   `xml:"Pattern"`       // mo.TypeString
	Items         string   `xml:"Items"`         // mo.TypeArray The value must be objected
	MinItems      uint64   `xml:"MinItems"`      // mo.TypeArray minimum number of value in array
	MaxItems      uint64   `xml:"MaxItems"`      // mo.TypeArray maximum number of value in array
	UniqueItems   bool     `xml:"UniqueItems"`   // mo.TypeArray The value in the array must be unique
	MinProperties uint64   `xml:"MinProperties"` // mo.TypeObject minimum number of fields in object
	MaxProperties uint64   `xml:"MaxProperties"` // mo.TypeObject maximum number of fields in object
	Required      []string `xml:"Required>Name"` // mo.TypeObject
}

type Configure struct {
	Name     string   `xml:"Name,attr"`
	Required []string `xml:"Required>Name"`
	Unique   []string `xml:"Unique>Name"` // Need vdx.Init called
	Fields   []Field  `xml:"Fields>Field"`
}

func (c Configure) FieldMap() map[string]Field {
	field := make(map[string]Field, len(c.Fields))
	for i := 0; i < len(c.Fields); i++ {
		field[c.Fields[i].Name] = c.Fields[i]
	}
	return field
}

func (c Field) Validate(data interface{}) error {
	switch c.Type {
	case mo.TypeObjectId:
		return c.ValidateObjectId(data)
	case mo.TypeInt, mo.TypeInt64, mo.TypeDouble, mo.TypeDate:
		return c.ValidateNumber(data)
	case mo.TypeString:
		return c.ValidateString(data)
	case mo.TypeArray:
		return c.ValidateArray(data)
	case mo.TypeObject:
		return c.ValidateObject(data)
	default:
		return nil
	}
}

func (c Field) ValidateObjectId(data interface{}) error {
	var err error

	switch v := data.(type) {
	case mo.ObjectID:
		if !v.IsZero() {
			return nil
		}
	case string:
		var id mo.ObjectID
		id, err = mo.ObjectIDFromHex(v)
		if err == nil && !id.IsZero() {
			return nil
		}
	}

	if err != nil {
		return err
	}

	if err = c.isRequired(); err != nil {
		return err
	}

	return mo.ErrNilObjectId
}

func (c Field) ValidateNumber(data interface{}) error {
	if data == nil {
		if err := c.isRequired(); err != nil {
			return err
		}
	}
	var f float64
	switch v := data.(type) {
	case int64:
		f = float64(v)
	case float64:
		f = v
	case int32:
		f = float64(v)
	case mo.DateTime:
		f = float64(v)
	case int:
		f = float64(v)
	case float32:
		f = float64(v)
	default:
		return fmt.Errorf("unsupport type: %s", reflect.TypeOf(data).Kind())
	}
	if c.Minimum != 0 {
		if c.Maximum == 0 {
			c.Maximum = math.MaxFloat64
		}
		if f < c.Minimum {
			return fmt.Errorf("%s value %.2f must be > %.2f", c.Name, f, c.Minimum)
		}
		if f > c.Maximum {
			return fmt.Errorf("%s value %.2f must be < %.2f", c.Name, f, c.Maximum)
		}
	}
	if len(c.Enums) > 0 {
		for i := 0; i < len(c.Enums); i++ {
			v, err := strconv.ParseFloat(c.Enums[i], 64)
			if err != nil {
				return err
			}
			if data == v {
				return nil
			}
		}
		return fmt.Errorf("%s can only be one of the %v values", c.Name, c.Enums)
	}
	return nil
}

func (c Field) ValidateString(data interface{}) error {
	v, ok := data.(string)
	if !ok {
		return fmt.Errorf("%s must be string: %s", c.Name, reflect.TypeOf(data).Kind())
	}
	if v == "" {
		if err := c.isRequired(); err != nil {
			return err
		}
	}
	if c.MinLength > 0 {
		if c.MaxLength == 0 {
			c.MaxLength = math.MaxUint64
		}
		if l := uint64(len(v)); l < c.MinLength {
			return fmt.Errorf("%s length %d must be > %d", c.Name, l, c.MinLength)
		}
		if l := uint64(len(v)); l > c.MaxLength {
			return fmt.Errorf("%s length %d must be < %d", c.Name, l, c.MaxLength)
		}
	}
	if len(c.Enums) > 0 {
		for i := 0; i < len(c.Enums); i++ {
			if c.Enums[i] == data {
				return nil
			}
		}
		return fmt.Errorf("%s can only be one of the %v values", c.Name, c.Enums)
	}
	if c.Pattern != "" {
		matched, err := regexp.MatchString(c.Pattern, v)
		if err != nil {
			return err
		}
		if !matched {
			return fmt.Errorf("not matched for %s value %s in %s", c.Name, v, c.Pattern)
		}
	}
	return nil
}

func (c Field) ValidateArray(data interface{}) error {
	v, ok := data.([]interface{})
	if !ok {
		return fmt.Errorf("%s must be []interface{}: %s", c.Name, reflect.TypeOf(data).Kind())
	}

	if len(v) == 0 {
		if err := c.isRequired(); err != nil {
			return err
		}
	}

	if c.Items == "object" {
		for i := 0; i < len(v); i++ {
			if _, o := v[i].(map[string]interface{}); !o {
				return fmt.Errorf("%s the %v must be objected, id: %d", c.Name, v[i], i)
			}
		}
	}
	if c.MinItems > 0 {
		if c.MaxItems == 0 {
			c.MaxItems = math.MaxUint64
		}
		if i := uint64(len(v)); i < c.MinItems {
			return fmt.Errorf("%s items %d must be > %d", c.Name, i, c.MinItems)
		}
		if i := uint64(len(v)); i > c.MaxItems {
			return fmt.Errorf("%s items %d must be < %d", c.Name, i, c.MaxItems)
		}
	}

	if c.UniqueItems {
		tmp := make(map[interface{}]struct{}, len(v))
		for i := 0; i < len(v); i++ {
			tmp[v[i]] = struct{}{}
		}
		if len(tmp) != len(v) {
			return fmt.Errorf("%s value in the array must be unique", c.Name)
		}
		tmp = nil
	}

	return nil
}

func (c Field) ValidateObject(data interface{}) error {
	if data == nil {
		if err := c.isRequired(); err != nil {
			return err
		}
	}
	v, ok := data.(map[string]interface{})
	if !ok {
		return fmt.Errorf("%s must be map[string]interface: %s", c.Name, reflect.TypeOf(data).Kind())
	}
	if c.MinProperties > 0 {
		if c.MaxProperties == 0 {
			c.MaxProperties = math.MaxUint64
		}
		if i := uint64(len(v)); i < c.MinProperties {
			return fmt.Errorf("%s properties %d must be > %d", c.Name, i, c.MinProperties)
		}
		if i := uint64(len(v)); i > c.MaxProperties {
			return fmt.Errorf("%s properties %d must be < %d", c.Name, i, c.MaxProperties)
		}
	}
	if len(c.Required) > 0 {
		return Required(c.Required, v)
	}
	return nil
}

func (c Field) isRequired() error {
	for i := 0; i < len(c.Required); i++ {
		if c.Name == c.Required[i] {
			return fmt.Errorf("%s required", c.Name)
		}
	}
	return nil
}

func Required(field []string, data map[string]interface{}) error {
	for i := 0; i < len(field); i++ {
		if v, ok := data[field[i]]; ok {
			if s, o := v.(string); o && strings.TrimSpace(s) == "" {
				return fmt.Errorf("%s required", field[i])
			}
		} else {
			return fmt.Errorf("%s required", field[i])
		}
	}
	return nil
}

var (
	config   map[string]Configure // If LoadMustConfigure is called, it can only be read.
	isLoaded bool
)

func GetConfigure() map[string]Configure {
	cfg := make(map[string]Configure, len(config))
	for name, configure := range config {
		cfg[name] = configure
	}
	return cfg
}

func LoadMustConfigure(path string) {
	if isLoaded {
		return
	}
	config = make(map[string]Configure, 1024)
	err := filepath.Walk(path, func(filePath string, f os.FileInfo, err error) error {
		if strings.HasSuffix(filePath, ".xml") {
			cfg, err := ReadXML(filePath)
			if err != nil {
				return err
			}
			config[cfg.Name] = cfg
		}
		return nil
	})
	if err != nil {
		panic(err)
	}
	isLoaded = true
}

func ReadXML(path string) (Configure, error) {
	content, err := os.ReadFile(path)
	if err != nil {
		return Configure{}, err
	}
	var cfg Configure
	return cfg, xml.Unmarshal(content, &cfg)
}

// Is 通过 name 的配置校验 data 内的数据是否能格式化为相应的数据类型
// 如果解析失败时则返回错误
// 通常 validate 用于添加数据时的校验
func Is(data map[string]interface{}, name string) error {
	if !isLoaded {
		return errors.New("uninitialized")
	}
	v, ok := config[name]
	if !ok {
		return fmt.Errorf("config not found: %s", name)
	}
	return validate(data, v)
}

func IsList(data []map[string]interface{}, name string) error {
	for i := 0; i < len(data); i++ {
		if err := Is(data[i], name); err != nil {
			return err
		}
	}
	return nil
}

func validate(data map[string]interface{}, c Configure) error {
	// if err := Required(c.Required, data); err != nil {
	// 	return err
	// }

	field := c.FieldMap()

	for k, v := range data {
		// if Field does not exist key in Data, it will be ignored
		got, ok := field[k]
		if !ok {
			continue
		}
		if err := got.Validate(v); err != nil {
			return err
		}
	}

	return nil
}
