package mo

import (
	"context"
	"strings"
	"time"

	"go.mongodb.org/mongo-driver/v2/bson"
)

const (
	OID = "_id"
)

type oid struct{}

// String fmt usd only. import use OID
func (oid) String() string {
	return OID
}

func (oid) New() ObjectID {
	return bson.NewObjectID()
}

func (oid) From(hex string) (ObjectID, error) {
	id, err := bson.ObjectIDFromHex(hex)
	if err != nil {
		return NilObjectID, err
	}
	if id.IsZero() {
		return NilObjectID, ErrInvalidHex
	}
	return id, nil
}

func (oid) FromMust(hex string) ObjectID {
	id, err := bson.ObjectIDFromHex(hex)
	if err != nil {
		panic(err)
	}
	if id.IsZero() {
		panic(ErrInvalidHex)
	}
	return id
}

func (o oid) IsValid(hex string) bool {
	_, err := o.From(hex)
	return err == nil
}

var (
	ID = oid{} // ID 用于 ObjectID 的 API
)

// UnmarshalExtJSON 将 json 字符串解析为 bson 类型
// data 为字符串字节, canonical 是否为严格类型, val 需要绑定的类型
// 可参考 https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#examples
// 与 json.Unmarshal 不同的是: 当 val 为 D / M 时, 会保留 key 的顺序. 但由于 Go 语言 for 循环 map 时会打乱顺序, 因此如果对 key 的顺序
// 有要求时请使用 D 作为绑定类型
// 用法参见 TestUnmarshalExtJSON
func UnmarshalExtJSON(data []byte, canonical bool, val any) error {
	return bson.UnmarshalExtJSON(data, canonical, val)
}

// MarshalExtJSON 将 bson 结构编码为 json 类型
func MarshalExtJSON(val any, canonical, escapeHTML bool) ([]byte, error) {
	return bson.MarshalExtJSON(val, canonical, escapeHTML)
}

// Marshal 将 v 作为 bson 进行编码
func Marshal(v any) ([]byte, error) {
	return bson.Marshal(v)
}

// Unmarshal 将 data 作为 bson 解析到 val
func Unmarshal(data []byte, val any) error {
	return bson.Unmarshal(data, val)
}

func NewDateTime() DateTime {
	return NewDateTimeFromTime(time.Now())
}

func NewDateTimeFromTime(t time.Time) DateTime {
	return bson.NewDateTimeFromTime(t.Local())
}

func NewDecimal128(h, l uint64) Decimal128 {
	return bson.NewDecimal128(h, l)
}

// ResolveIndexName 从 cursor 中解析出索引名称, 索引名称见 IndexName
// bool 表示 unique
func ResolveIndexName(cursor *Cursor) (map[string]bool, error) {
	var idxList A
	if err := CursorDecodeAll(cursor, &idxList); err != nil {
		return nil, err
	}
	idxMap := make(map[string]bool)
	for _, idx := range idxList {
		arr, ok := idx.(D)
		if !ok {
			panic(arr)
		}
		attrMap := ToMFast(arr)
		if name, on := attrMap["name"].(string); on {
			if strings.HasPrefix(name, OID) {
				continue
			}
			name = strings.TrimSuffix(name, IndexSuffix)
			if unique, o := attrMap["unique"].(bool); o {
				idxMap[name] = unique
			} else {
				idxMap[name] = false
			}
		}
	}
	return idxMap, nil
}

func ResolveDateTime(value string) (DateTime, error) {
	return ResolveDateTimeFrom(ISODate, value)
}

func ResolveDateTimeFrom(layout string, value string) (DateTime, error) {
	t, err := time.ParseInLocation(layout, value, time.Local)
	if err != nil {
		return 0, err
	}
	return NewDateTimeFromTime(t), nil
}

func CursorDecodeAll(cursor *Cursor, v any) error {
	ctx, cancel := context.WithTimeout(context.Background(), DefaultTimout)
	defer func() {
		_ = cursor.Close(ctx)
		cancel()
	}()
	return cursor.All(ctx, v)
}

func HasOptWith(pipe Pipeline, opt string) (int, any, bool) {
	for _, p := range pipe {
		if idx, ok := HasOptIn(p, opt); ok {
			return idx, p[idx].Value, ok
		}
	}
	return -1, nil, false
}

func HasOptIn(doc D, operator string) (int, bool) {
	for i, ele := range doc {
		if ele.Key == operator {
			return i, true
		}
	}
	return -1, false
}

func Decode(m, v any) error {
	b, err := Marshal(m)
	if err != nil {
		return err
	}
	return Unmarshal(b, v)
}

func DecodeAll[V, T any](m []V, dst *[]T) error {
	if len(*dst) < len(m) {
		*dst = make([]T, len(m))
	}
	for i, row := range m {
		var v T
		if err := Decode(row, &v); err != nil {
			return err
		}
		(*dst)[i] = v
	}
	return nil
}

func DecodeJson(j any, v any) error {
	b, err := MarshalExtJSON(j, true, false)
	if err != nil {
		return err
	}
	return UnmarshalExtJSON(b, true, v)
}

func DeepCopy(src M) (M, error) {
	var dst M
	return dst, Decode(src, &dst)
}

func ToA[T any](src []T) A {
	s := make(A, len(src))
	for i := 0; i < len(src); i++ {
		s[i] = src[i]
	}
	return s
}
