package ii

import (
	"fmt"
	"reflect"
	"strings"

	"golib/v2/features/mo"
)

var (
	errUnknownFiled = func(name Name, key string) error {
		return fmt.Errorf("unknown filed: %s.%s", name, key)
	}
)

// ItemInfo XML 配置, 每个 XML 应当包含 _id 字段
type ItemInfo struct {
	Name   Name        `xml:"Name,attr"`
	Label  string      `xml:"Label,attr"`
	Fields []FieldInfo `xml:"Fields>Field"`

	FieldMap    map[string]int
	RequiredMap map[string]int // 必填
	UniqueMap   map[string]int // 需要调用 SetUnique 设置唯一键
}

func (c *ItemInfo) ForkName(name string) Name {
	return Name(c.Name.Database() + "." + name)
}

// Open 使用 Name 包含的数据库和表然后打开一个操作
func (c *ItemInfo) Open(client *mo.Client) *mo.Shortcut {
	return mo.NewShortcut(client.Database(c.Name.Database()).Collection(c.Name.Collection()))
}

func (c *ItemInfo) CopyMap(doc mo.M) (mo.M, error) {
	m := make(mo.M)
	for key, val := range doc {
		switch key {
		case ID, Creator, CreationTime, LastModified, LastUpdater:
			continue
		}
		field, ok := c.Field(key)
		if !ok {
			continue
		}
		v, err := field.Convert(val)
		if err != nil {
			return nil, err
		}
		m[key] = v
	}
	return mo.DeepMapCopy(m)
}

// PrepareNew 创一个列表, 包含所有 Fields 的 name 和默认值
func (c *ItemInfo) PrepareNew() mo.D {
	f := make(mo.D, 0, len(c.Fields))
	for _, field := range c.Fields {
		if field.Name == mo.ID.Key() {
			if !field.Required {
				continue
			}
		}
		f = append(f, mo.E{Key: field.Name, Value: field.DefaultValue()})
	}
	return f
}

// PrepareInsert 准备插入的数据
func (c *ItemInfo) PrepareInsert(doc mo.M, u User) error {
	for key, val := range doc {
		field, ok := c.Field(key)
		if !ok {
			// 特殊处理 _id
			if key == mo.ID.Key() {
				if oid, ok := val.(mo.ObjectID); !(ok && !oid.IsZero()) {
					return fmt.Errorf("invalid ObjectID: %s(%v)", reflect.TypeOf(val), val)
				}
			}
			// 不允许添加配置文件中不存在的字段
			return errUnknownFiled(c.Name, key)
		}
		// 校验和格式化数据
		if err := field.Validate(val); err != nil {
			val, err = field.Convert(val)
			if err != nil {
				return err
			}
		}
		doc[field.Name] = val
	}

	// 填充配置文件中已存在的字段
	fList := c.PrepareNew()
	for _, e := range fList {
		if _, ok := doc[e.Key]; ok {
			continue
		}
		doc[e.Key] = e.Value
	}

	// 校验必填
	for key := range c.RequiredMap {
		if _, ok := doc[key]; !ok {
			return errRequired(key, doc)
		}
	}

	doc[Creator] = u.ID()
	doc[CreationTime] = mo.NewDateTime()
	return nil
}

func (c *ItemInfo) prepareUpdateObject(k string, v any) (any, error) {
	fieldName, subFieldName, ok := strings.Cut(k, ".")
	if !ok {
		return nil, errUnknownFiled(c.Name, k)
	}
	field, fo := c.Field(fieldName)
	if !fo {
		return nil, errUnknownFiled(c.Name, fieldName)
	}
	if field.Type != mo.TypeObject {
		return nil, errTypeReturn(&field, v)
	}
	subField, so := field.SubField(subFieldName)
	if !so {
		return nil, errUnknownFiled(c.Name, k)
	}
	if err := subField.Validate(v); err == nil {
		return v, nil
	}
	return subField.Convert(v)
}

func (c *ItemInfo) prepareUpdateArray(k string, v any) (any, error) {
	name := strings.Split(k, ".")
	if len(name) < 2 {
		return nil, errUnknownFiled(c.Name, k)
	}
	fieldName := name[0]
	field, ok := c.Field(fieldName)
	if !ok {
		return nil, errUnknownFiled(c.Name, fieldName)
	}
	if field.Type != mo.TypeArray {
		return nil, errTypeReturn(&field, v)
	}
	if field.Items == FieldItemsObject {
		if len(name) != 3 {
			return nil, errUnknownFiled(c.Name, k)
		}
		subFieldName := name[2]
		subField, o := field.SubField(subFieldName)
		if !o {
			return nil, errUnknownFiled(c.Name, fieldName+"."+subFieldName)
		}
		if err := subField.Validate(v); err == nil {
			return v, nil
		}
		return subField.Convert(v)
	} else {
		if err := field.Validate(v); err == nil {
			return v, nil
		}
		return field.Convert(v)
	}
}

func (c *ItemInfo) PrepareUpdater(updater mo.D, u User) error {
	hasSetter := false
	for i, e := range updater {
		switch e.Key {
		case mo.PoSet:
			doc, err := mo.Convert.ME(e.Value.(mo.D))
			if err != nil {
				return err
			}
			if err = c.PrepareUpdate(doc); err != nil {
				return err
			}
			if e.Key == mo.PoSet {
				doc[LastUpdater] = u.ID()
				doc[LastModified] = mo.NewDateTime()
				hasSetter = true
			}
			update, err := mo.Convert.DE(doc)
			if err != nil {
				return err
			}
			updater[i] = mo.E{Key: e.Key, Value: update}
		default:
			for _, ev := range e.Value.(mo.D) {
				// 对于非 mo.PoSet 类型的更新, 仅判断字段是否存在, 不再为其检测和转换数据类型
				if _, ok := c.Field(ev.Key); !ok {
					return errUnknownFiled(c.Name, ev.Key)
				}
			}
		}
	}
	if !hasSetter {
		updater = append(mo.D{
			{Key: mo.PoSet, Value: mo.D{
				{Key: LastUpdater, Value: u.ID()},
				{Key: LastModified, Value: mo.NewDateTime()},
			}}},
			updater...,
		)
	}
	return nil
}

// PrepareUpdate 准备更新的数据
func (c *ItemInfo) PrepareUpdate(doc mo.M) error {
	for k, v := range doc {
		if k == mo.ID.Key() {
			return fmt.Errorf("_id value can not be update")
		}
		var err error
		field, ok := c.Field(k)
		if !ok {
			switch strings.Count(k, ".") {
			case 1:
				if v, err = c.prepareUpdateObject(k, v); err != nil {
					return err
				}
			case 2:
				if v, err = c.prepareUpdateArray(k, v); err != nil {
					return err
				}
			default:
				return errUnknownFiled(c.Name, k)
			}
		} else {
			if err = field.Validate(v); err != nil {
				v, err = field.Convert(v)
				if err != nil {
					return err
				}
			}
		}
		doc[k] = v
	}
	return nil
}

// PrepareFilter 检查 key 是否包在 itemName 中, 防止 SQL 注入
func (c *ItemInfo) PrepareFilter(filter mo.D) error {
	for _, ele := range filter {
		// 不检查包含 . 的字段, 用于子 map 查找
		if strings.ContainsRune(ele.Key, '.') {
			continue
		}
		if _, ok := c.Field(ele.Key); !ok && !strings.HasPrefix(ele.Key, "$") {
			return errUnknownFiled(c.Name, ele.Key)
		}
		// if array, ok := ele.Value.(mo.A); ok {
		// 	for _, ai := range array {
		// 		ai.(mo.D)
		// 	}
		//
		// }
	}
	return nil
}

func (c *ItemInfo) Field(name string) (FieldInfo, bool) {
	if field, ok := internalField[name]; ok {
		if idx, o := c.FieldMap[name]; o {
			return c.Fields[idx], true
		}
		return field, true
	}
	idx, ok := c.FieldMap[name]
	if !ok {
		return FieldInfo{}, false
	}
	return c.Fields[idx], true
}

func (c *ItemInfo) FieldType(t mo.Type) []FieldInfo {
	fields := make([]FieldInfo, 0)
	for _, field := range c.Fields {
		if field.Type == t {
			fields = append(fields, field)
		}
	}
	return fields
}
