package ii

import (
	"context"
	"encoding/xml"
	"os"
	"slices"
	"time"

	"git.simanc.com/software/golib/v4/features/mo"
	"git.simanc.com/software/golib/v4/gio"
)

const (
	ConfigSuffix = ".xml"
)

// LoadItems 从 path 中读取并解析 XML 配置
func LoadItems(path string, dbAlias map[string]string) (Items, error) {
	name, err := gio.ReadDir(path, ConfigSuffix)
	if err != nil {
		return nil, err
	}
	items := &ItemIndex{
		itemMap: map[string]*ItemInfo{},
		dbAlias: dbAlias,
	}
	for i := 0; i < len(name); i++ {
		var itemInfo *ItemInfo
		itemInfo, err = ReadFile(name[i])
		if err != nil {
			return nil, err
		}
		if dbAlias != nil {
			if alias, found := dbAlias[itemInfo.Name.DbName()]; found {
				itemInfo.Name.SetAlias(alias)
			}
		}
		items.itemMap[itemInfo.Name.ItemName()] = itemInfo
	}
	return items, nil
}

// ReadFile 解析 name 至 ItemInfo
// 如果需要 FieldInfo.Unique 生效, 需要调用 SetUnique
func ReadFile(name string) (*ItemInfo, error) {
	b, err := os.ReadFile(name)
	if err != nil {
		return nil, err
	}
	return ReadFrom(b)
}

func ReadFrom(b []byte) (*ItemInfo, error) {
	var itemInfo ItemInfo
	if err := xml.Unmarshal(b, &itemInfo); err != nil {
		return nil, err
	}
	if err := itemInfo.init(); err != nil {
		return nil, err
	}
	return &itemInfo, nil
}

// SetUnique 设置唯一键
// 注意: 为了降低初始化 XML 配置文件时的耦合度, 因此只能通过此方法设置唯一键. 如果通过软件实现唯一值, 那么将无法保证原子性
// 实现方法: 取出已存在的 index, 然后与 ItemInfo 中的 uniqueMap 比较:
// 删除 uniqueMap 中不存在的字段, 跳过 uniqueMap 中已存在的字段, 然后设置 uniqueMap 存在但 index 中不存在的字段为索引
func SetUnique(info *ItemInfo, client *mo.Client) error {
	ctx, cancel := context.WithTimeout(context.Background(), mo.DefaultTimout)
	defer cancel()

	coll := client.Database(info.Name.DbName()).Collection(info.Name.Collection())
	operator := coll.Indexes()

	cursor, err := operator.List(ctx)
	if err != nil {
		return err
	}
	oldIndexMap, err := mo.ResolveIndexName(cursor)
	if err != nil {
		return err
	}
	itemUniques := info.getUniques()
	for oldName := range oldIndexMap {
		if slices.Contains(itemUniques, oldName) {
			continue
		}
		// 删除 info 中不存在的索引
		if err = operator.DropOne(ctx, mo.IndexName(oldName)); err != nil {
			return err
		}
	}
	var indexList []mo.IndexModel
	for _, key := range itemUniques {
		if _, ok := oldIndexMap[key]; ok {
			continue
		}
		indexList = append(indexList, mo.NewIndex(key, true))
	}
	if len(indexList) == 0 {
		return nil
	}
	_, err = operator.CreateMany(ctx, indexList)
	return err
}

func SetItemsUnique(items Items, client *mo.Client) error {
	for _, item := range items.All() {
		if err := SetUnique(item, client); err != nil {
			return err
		}
	}
	return nil
}

func NewTime(d time.Duration) mo.DateTime {
	return mo.NewDateTimeFromTime(time.Now().Add(d))
}

func AppendTimeRange(matcher *mo.Matcher, start, end time.Time) {
	matcher.Gte(CreationTime, mo.NewDateTimeFromTime(start))
	matcher.Lte(CreationTime, mo.NewDateTimeFromTime(end))
}
