package svc

import (
	"fmt"
	"strings"
	"time"

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

type Service struct {
	usr User
}

// Svc need prepare mdb.Client and ii.LoadItemInfo first
// The second params can not be Nil if called original methods
func Svc(user User) *Service {
	return &Service{usr: user}
}

func (c Service) InsertOne(name string, doc interface{}) (mo.ObjectID, error) {
	coll, err := c.linkColl(name)
	if err != nil {
		return mo.NilObjectID, err
	}

	ctx, cancel := DefaultCtx()
	defer cancel()

	retId, err := coll.InsertOne(ctx, doc)
	if err != nil {
		return mo.NilObjectID, err
	}

	id, ok := retId.InsertedID.(mo.ObjectID)
	if ok && !id.IsZero() {
		if err = c.insertOneAppendMore(name, id); err == nil {
			return id, nil
		}
	}

	del := mo.D{{Key: _Id, Value: id}}
	if err = c.DeleteOne(name, del); err != nil {
		panic(err)
	}

	return mo.NilObjectID, mo.ErrNilObjectId
}

func (c Service) InsertMany(name string, doc []interface{}) ([]mo.ObjectID, error) {
	coll, err := c.linkColl(name)
	if err != nil {
		return nil, err
	}

	ctx, cancel := DefaultCtx()
	defer cancel()

	retId, err := coll.InsertMany(ctx, doc)
	if err != nil {
		return nil, err
	}

	ids := make([]mo.ObjectID, len(retId.InsertedIDs))

	for i := 0; i < len(retId.InsertedIDs); i++ {
		id, ok := retId.InsertedIDs[i].(mo.ObjectID)
		if ok {
			ids[i] = id
		} else {
			filter := mo.D{{Key: mo.VIn, Value: retId.InsertedIDs}}
			if err = c.DeleteMany(name, mo.D{{Key: _Id, Value: filter}}); err == nil {
				return nil, fmt.Errorf("ObjectId wrong format")
			} else {
				return nil, fmt.Errorf("ObjectId wrong format and %s", err)
			}
		}
	}

	return ids, c.insertManyAppendMore(name, ids)
}

func (c Service) FindOne(name string, filter interface{}) (mo.M, error) {
	coll, err := c.linkColl(name)
	if err != nil {
		return nil, err
	}

	ctx, cancel := DefaultCtx()
	defer cancel()

	ret := coll.FindOne(ctx, filter)
	if err = ret.Err(); err != nil {
		return nil, err
	}

	var result mo.M
	return result, ret.Decode(&result)
}

func (c Service) FindMany(name string, filter interface{}, opts ...*mo.FindOptions) ([]mo.M, error) {
	coll, err := c.linkColl(name)
	if err != nil {
		return nil, err
	}

	ctx, cancel := DefaultCtx()
	defer cancel()

	ret, err := coll.Find(ctx, filter, opts...)
	if err != nil {
		return nil, err
	}
	var result []mo.M
	return result, ret.All(ctx, &result)
}

func (c Service) UpdateOne(name string, filter, update interface{}) (mo.ObjectID, error) {
	coll, err := c.linkColl(name)
	if err != nil {
		return mo.NilObjectID, err
	}

	ctx, cancel := DefaultCtx()
	defer cancel()

	ret := coll.FindOneAndUpdate(ctx, filter, update)
	if err = ret.Err(); err != nil {
		return mo.NilObjectID, err
	}

	var r mo.M
	if err = ret.Decode(&r); err != nil {
		panic(err)
	}
	return r[_Id].(mo.ObjectID), nil
}

func (c Service) UpdateById(name string, id mo.ObjectID, update interface{}) (mo.ObjectID, error) {
	coll, err := c.linkColl(name)
	if err != nil {
		return mo.NilObjectID, err
	}

	ctx, cancel := DefaultCtx()
	defer cancel()

	ret := coll.FindOneAndUpdate(ctx, mo.D{{Key: _Id, Value: id}}, update)
	if err = ret.Err(); err != nil {
		return mo.NilObjectID, err
	}

	var r mo.M
	if err = ret.Decode(&r); err != nil {
		panic(err)
	}
	return r[_Id].(mo.ObjectID), nil
}

func (c Service) UpdateMany(name string, filter, update interface{}) error {
	coll, err := c.linkColl(name)
	if err != nil {
		return err
	}

	ctx, cancel := DefaultCtx()
	defer cancel()

	_, err = coll.UpdateMany(ctx, filter, update)
	return err
}

func (c Service) DeleteOne(name string, filter interface{}) error {
	coll, err := c.linkColl(name)
	if err != nil {
		return err
	}

	ctx, cancel := DefaultCtx()
	defer cancel()

	_, err = coll.DeleteOne(ctx, filter)
	return err
}

func (c Service) DeleteMany(name string, filter interface{}) error {
	coll, err := c.linkColl(name)
	if err != nil {
		return err
	}

	ctx, cancel := DefaultCtx()
	defer cancel()

	_, err = coll.DeleteMany(ctx, filter)
	return err
}

func (c Service) CountDocuments(name string, filter interface{}) (int64, error) {
	coll, err := c.linkColl(name)
	if err != nil {
		return 0, err
	}

	ctx, cancel := DefaultCtx()
	defer cancel()

	return coll.CountDocuments(ctx, filter)
}

func (c Service) Aggregate(name string, pipe mo.Pipeline, opts ...*mo.AggregateOptions) (*mo.Cursor, error) {
	coll, err := c.linkColl(name)
	if err != nil {
		return nil, err
	}

	ctx, cancel := DefaultCtx()
	defer cancel()

	return coll.Aggregate(ctx, pipe, opts...)
}

func (c Service) linkColl(name string) (*mo.Collection, error) {
	if !c.usr.Valid() {
		return nil, ErrInvalidUser
	}
	dbName := ii.NewName(name)
	return mongoDB.Client().Database(dbName.DbName()).Collection(dbName.CollName()), nil
}

func (c Service) insertOneAppendMore(name string, id mo.ObjectID) (err error) {
	_, err = c.UpdateById(name, id, mo.D{{Key: mo.PSet, Value: mo.D{
		{Key: _Creator, Value: c.usr.GetId()},
	}}})
	return
}

func (c Service) insertManyAppendMore(name string, id []mo.ObjectID) (err error) {
	err = c.UpdateMany(name, mo.D{{Key: _Id, Value: mo.D{{Key: mo.VIn, Value: id}}}}, mo.D{{Key: mo.PSet, Value: mo.D{
		{Key: _Creator, Value: c.usr.GetId()},
	}}})
	return
}

func (c Service) filterValue(item ii.Item, update map[string]interface{}) error {
	for key, value := range update {

		field, err := item.GetField(key)
		if err != nil {
			return err
		}

		v, err := c.GetValueByType(field.GetType(), value)
		if err != nil {
			return err
		}

		update[key] = v
	}

	return nil
}

func (c Service) getString(value interface{}) (string, error) {
	switch v := value.(type) {
	case string:
		switch v {
		case "curtime":
			v = time.Now().Format(mo.DateTimeLayout)
		case "curdate":
			v = time.Now().Format("2006-01-06")
		case "curuser", "cursn", "curusersn":
			v = c.usr.GetId().Hex()
		case "curusername":
			v = c.usr.GetUserName()
		case "curname":
			v = c.usr.GetName()
		default:
			if strings.HasPrefix(v, "cur") {
				return "", ErrUnknownType(value)
			}
		}
	}
	return getFormatString(value), nil
}

func (c Service) defaultTypeValue(t mo.Type) interface{} {
	switch t {
	case mo.TypeDouble:
		return float64(0)
	case mo.TypeString:
		return ""
	case mo.TypeObject:
		return map[string]interface{}{}
	case mo.TypeArray:
		return []interface{}{}
	case mo.TypeBinary:
		return []byte{}
	case mo.TypeObjectId:
		return mo.NilObjectID
	case mo.TypeBoolean:
		return false
	case mo.TypeDate:
		return mo.DateTime(0)
	case mo.TypeNull:
		return nil
	case mo.TypeRegex:
		return mo.Regex{}
	case mo.TypeJavaScript:
		return mo.JavaScript("")
	case mo.TypeInt:
		return int32(0)
	case mo.TypeInt64:
		return int64(0)
	case mo.TypeDecimal128:
		f, _ := getDecimal128("0,0")
		return f
	case mo.TypeMaxKey:
		return mo.MaxKey{}
	case mo.TypeMinKey:
		return mo.MinKey{}
	default:
		return nil
	}
}

func (c Service) GetValueByType(t mo.Type, v interface{}) (interface{}, error) {
	if v == nil {
		return c.defaultTypeValue(t), nil
	}
	switch t {
	case mo.TypeDouble:
		return getDouble(v)
	case mo.TypeString:
		return c.getString(v)
	case mo.TypeObject:
		return getObject(v)
	case mo.TypeArray:
		return getArray(v)
	case mo.TypeBinary:
		return getBinary(v)
	case mo.TypeObjectId:
		return getObjectId(v)
	case mo.TypeBoolean:
		return getBool(v)
	case mo.TypeDate:
		return getDate(v)
	case mo.TypeNull:
		return nil, nil
	case mo.TypeRegex:
		return getRegex(v)
	case mo.TypeJavaScript:
		return getJavaScript(v)
	case mo.TypeInt:
		return getInt32(v)
	case mo.TypeInt64:
		return getInt64(v)
	case mo.TypeDecimal128:
		return getDecimal128(v)
	case mo.TypeMaxKey:
		return mo.MaxKey{}, nil
	case mo.TypeMinKey:
		return mo.MinKey{}, nil
	default:
		return nil, ErrUnknownType(v)
	}
}

// TODO 更改为使用原生 Lookup 语句
func (c Service) getLookup(field ii.Field, value interface{}) (string, interface{}) {
	look, ok := field.GetLookup()
	if !ok {
		return look.AS, "no_Lookup_value"
	}

	ret, err := c.FindOne(look.From, mo.D{{Key: look.Condition, Value: mo.D{{Key: mo.VEq, Value: value}}}})
	if err != nil {
		return look.AS, err.Error()
	}

	return look.AS, ret[look.Need]
}

func (c Service) FormatValue(field ii.Field, value interface{}, doc map[string]interface{}) {
	switch field.GetModel() {
	case ii.TypeDefault:
		return
	case ii.TypeString:
		doc[field.GetName()] = getFormatString(value)
	case ii.TypeDate:
		doc[field.GetName()] = getFormatDate(value)
	case ii.TypeTime:
		doc[field.GetName()] = getFormatTime(value)
	case ii.TypeInt64:
		doc[field.GetName()] = getFormatInt64(value)
	case ii.TypeDouble:
		doc[field.GetName()] = getFormatFloat64(value)
	case ii.TypeLookup:
		k, v := c.getLookup(field, value)
		doc[k] = v
	}
	doc[field.GetName()+"_raw"] = value
}
