package ii

import (
	"encoding/xml"
	"errors"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"

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

type itemInfo struct {
	Name     Name        `xml:"Name,attr"` // main.user
	Label    string      `xml:"Label,attr"`
	Fields   []fieldInfo `xml:"Fields>Field"`
	fieldMap map[string]fieldInfo
}

func (c *itemInfo) Init() {
	lstFields := make([]fieldInfo, len(c.Fields))
	for idx, field := range c.Fields {
		if err := field.init(); err != nil {
			log.Panicf("LoadItemInfo.Init: %s -> %s", c.Name, err)
		}
		lstFields[idx] = field
	}
	c.Fields = lstFields
	fieldMap := make(map[string]fieldInfo, len(c.Fields))
	for _, field := range c.Fields {
		fieldMap[field.Name] = field
	}
	c.fieldMap = fieldMap
}

func (c itemInfo) GetName() Name {
	return c.Name
}

func (c itemInfo) GetLabel() string {
	return c.Label
}

func (c itemInfo) GetField(name string) (Field, error) {
	v, ok := c.fieldMap[name]
	if !ok {
		return nil, fmt.Errorf("unknown_field: %s", name)
	}
	return v, nil
}

func (c itemInfo) GetFields() []Field {
	field := make([]Field, len(c.Fields))
	for i := 0; i < len(c.Fields); i++ {
		field[i] = Field(c.Fields[i])
	}
	return field
}

func (c itemInfo) GetFieldMap() map[string]Field {
	im := make(map[string]Field, len(c.fieldMap))
	for k, v := range c.fieldMap {
		im[k] = v
	}
	return im
}

func (c itemInfo) GetFieldsName() []string {
	name := make([]string, 0, len(c.Fields))
	for _, f := range c.Fields {
		if f.Ignore {
			continue
		}
		name = append(name, f.Name)
	}
	return name
}

type fieldInfo struct {
	Name    string      `xml:"Name,attr"`
	Label   string      `xml:"Label"`
	Type    mo.Type     `xml:"Type,attr"`  // Data type
	Model   Model       `xml:"Model,attr"` // Format type
	Default string      `xml:"Default"`
	Ignore  bool        `xml:"Ignore,attr"` // 忽略此字段
	Enums   []string    `xml:"Enums>Enum"`
	Number  NumberValue `xml:"Number"`
	Lookup  Lookup      `xml:"Lookup"`
}

type NumberValue struct {
	Minimum int64 `xml:"Minimum,attr"`
	Maximum int64 `xml:"Maximum,attr"`
}

type Lookup struct {
	From      string `xml:"From,attr"`      // 需要关联的表: ums.user
	Condition string `xml:"Condition,attr"` // 字段条件: _id
	Need      string `xml:"Need,attr"`      // 获取结果中的字段: name
	AS        string `xml:"As,attr"`        // 需要生成的字段: _id_name
}

func (c fieldInfo) GetName() string {
	return c.Name
}

func (c fieldInfo) GetLabel() string {
	return c.Label
}

func (c fieldInfo) GetType() mo.Type {
	return c.Type
}

func (c fieldInfo) GetModel() Model {
	return c.Model
}

func (c fieldInfo) IsIgnore() bool {
	return c.Ignore
}

func (c fieldInfo) GetLookup() (Lookup, bool) {
	if c.Lookup.From == "" {
		return Lookup{}, false
	}

	// lookup := mo.D{{
	// 	Key: mo.PLookup, Value: mo.D{
	// 		{Key: "from", Value: c.Lookup.From},
	// 		{Key: "localField", Value: c.Lookup.Need},
	// 		{Key: "foreignField", Value: c.Lookup.Condition},
	// 		{Key: "as", Value: c.Lookup.AS}},
	// }}

	return c.Lookup, true
}

func (c fieldInfo) GetEnums() ([]string, bool) {
	return c.Enums, len(c.Enums) == 0
}

func (c fieldInfo) GetNumber() (min int64, max int64, ok bool) {
	return c.Number.Minimum, c.Number.Maximum, c.Number.Minimum != 0 && c.Number.Maximum != 0
}

func (c fieldInfo) GetDefaultValue() string {
	return c.Default
}

func (c *fieldInfo) init() error {
	if c.Name == "" {
		return errors.New("Field.Name does not exist")
	}
	if c.Label == "" {
		return errors.New("Field.Label does not exist")
	}
	// 关联显示
	if c.Lookup.From != "" {
		// 如果未指定本地字段则使用 Name
		if c.Lookup.Need == "" {
			c.Lookup.Need = c.Name
		}
		// 如果未指定远程字段则使用 Name
		if c.Lookup.Condition == "" {
			c.Lookup.Condition = c.Name
		}
		if c.Lookup.AS == "" {
			c.Lookup.AS = c.Name
		}
	}
	return nil
}

var (
	itemMap = &itemBody{infoMap: map[Name]itemInfo{}}
)

type itemBody struct {
	infoMap  map[Name]itemInfo
	isLoaded bool
}

func (c *itemBody) Init(itemInfo map[Name]itemInfo) {
	c.infoMap = itemInfo
	c.isLoaded = true
}

func (c itemBody) GetItem(name Name) (Item, bool) {
	if info, ok := c.infoMap[name]; ok {
		return info, true
	}
	return itemInfo{}, false
}

func (c itemBody) IsLoaded() bool {
	return c.isLoaded
}

func GetItemByName(name string) (Item, bool) {
	return itemMap.GetItem(NewName(name))
}

type Name string

func (c Name) DbName() string {
	if i := strings.Index(c.String(), "."); i != -1 {
		return c.String()[:i]
	}
	return mo.DefaultDbName
}

func (c Name) CollName() string {
	if i := strings.Index(c.String(), "."); i != -1 {
		return c.String()[i+1:]
	}
	return c.String()
}

func (c Name) String() string {
	return string(c)
}

func (c *Name) UnmarshalXMLAttr(attr xml.Attr) error {
	if attr.Value == "" {
		return fmt.Errorf("unknown name: %s", attr.Value)
	}
	*c = Name(attr.Value)
	return nil
}

// NewName 获取包含数据库的集合名称, 例如: main.user
// 当不包含 . 时则会补充 MongoDB 默认的数据库 test
func NewName(name string) Name {
	return Name(name)
}

// LoadItemInfo 初始化
func LoadItemInfo(path string) {
	if itemMap.IsLoaded() {
		return
	}
	info := make(map[Name]itemInfo, 512)
	err := filepath.Walk(path, func(filePath string, f os.FileInfo, err error) error {
		if strings.HasSuffix(filePath, ".xml") {
			oItemInfo, err := readItemInfoFromXml(filePath)
			if err != nil {
				return err
			}
			oItemInfo.Init()
			info[oItemInfo.Name] = oItemInfo
		}
		return nil
	})
	if err != nil {
		panic(err)
	}
	itemMap.Init(info)
}

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