package orm

import (
	"reflect"
	"time"
)

type Document struct {
	// Created time. updated/added automatically.
	Created time.Time `bson:"createdAt" json:"createdAt"`
	// Modified time. updated/added automatically.
	Modified time.Time `bson:"updatedAt" json:"updatedAt"`
	model    *Model    `bson:"-"`
	exists   bool      `bson:"-"`
	self     any       `bson:"-"`
}
type IDocument interface {
	Append(field string, a ...interface{}) error
	Pull(field string, a ...any) error
	Swap(field string, i, j int) error
	Delete() error
	Remove() error
	Save() error
	SaveWith(opts *SaveOptions) error
	setSelf(arg interface{})
	getExists() bool
	setExists(n bool)
	setModified(Modified time.Time)
	setCreated(Modified time.Time)
	getModified() time.Time
	getCreated() time.Time
	serializeToStore() any
	getModel() *Model
	setModel(m Model)
}

type SaveOptions struct {
	SetTimestamps bool
}

func (d *Document) getCreated() time.Time {
	return d.Created
}

func (d *Document) setCreated(Created time.Time) {
	d.Created = Created
}

func (d *Document) getModified() time.Time {
	return d.Modified
}

func (d *Document) setModified(Modified time.Time) {
	d.Modified = Modified
}
func (d *Document) setSelf(arg interface{}) {
	d.self = arg
}

func (d *Document) getModel() *Model {
	return d.model
}

func (d *Document) setModel(m Model) {
	d.model = &m
}

func (d *Document) getExists() bool {
	return d.exists
}
func (d *Document) setExists(n bool) {
	d.exists = n
}

// Delete - deletes a model instance from the database
func (d *Document) Delete() error {
	var err error
	val := valueOf(d.self)
	if val.Kind() == reflect.Slice {
		for i := 0; i < val.Len(); i++ {
			cur := val.Index(i)
			if err = doDelete(d, cur.Interface()); err != nil {
				return err
			}
		}
		return nil
	} else {
		return doDelete(d, d.self)
	}
}

// Remove - alias for Delete
func (d *Document) Remove() error {
	return d.Delete()
}

// SaveWith - updates this Model in the database,
// or inserts it if it doesn't exist, using the provided
// SaveOptions
func (d *Document) SaveWith(opts *SaveOptions) error {
	val := valueOf(d.self)
	if val.Kind() == reflect.Slice {
		for i := 0; i < val.Len(); i++ {
			cur := val.Index(i)
			if err := doSave(d.model.getColl(), !d.exists, opts, cur.Interface()); err != nil {
				return err
			}
		}
		return nil
	} else {
		return doSave(d.model.getColl(), !d.exists, opts, d.self)
	}
}

// Save - updates this Model in the database,
// or inserts it if it doesn't exist, using
// default SaveOptions
func (d *Document) Save() error {
	return d.SaveWith(&SaveOptions{
		SetTimestamps: true,
	})
}

func (d *Document) serializeToStore() any {
	return serializeIDs((d).self)
}

// Append appends one or more items to `field`.
// will error if this Model contains a reference
// to multiple documents, or if `field` is not a
// slice.
func (d *Document) Append(field string, a ...interface{}) error {
	var d0 IDocument = d
	d0.getCreated()
	rv := reflect.ValueOf(d.self)
	selfRef := rv
	rt := reflect.TypeOf(d.self)
	if selfRef.Kind() == reflect.Pointer {
		selfRef = selfRef.Elem()
		rt = rt.Elem()
	}
	if err := checkStruct(selfRef); err != nil {
		return err
	}
	_, origV, err := getNested(field, selfRef)
	if err != nil {
		return err
	}

	origRef := makeSettable(*origV, (*origV).Interface())
	fv := origRef
	if fv.Kind() == reflect.Pointer {
		fv = fv.Elem()
	}
	if fv.Kind() != reflect.Slice {
		return ErrNotASlice
	}
	for _, b := range a {
		val := reflect.ValueOf(incrementTagged(b))
		fv.Set(reflect.Append(fv, val))
	}
	return nil
}

// Pull - removes elements from the subdocument slice stored in `field`.
func (d *Document) Pull(field string, a ...any) error {
	rv := reflect.ValueOf(d.self)
	selfRef := rv
	rt := reflect.TypeOf(d.self)
	if selfRef.Kind() == reflect.Pointer {
		selfRef = selfRef.Elem()
		rt = rt.Elem()
	}
	if err := checkStruct(selfRef); err != nil {
		return err
	}
	_, origV, err := getNested(field, selfRef)
	if err != nil {
		return err
	}

	origRef := makeSettable(*origV, (*origV).Interface())
	fv := origRef
	if fv.Kind() == reflect.Pointer {
		fv = fv.Elem()
	}
	if fv.Kind() != reflect.Slice {
		return ErrNotASlice
	}
	for _, b := range a {
	inner:
		for i := 0; i < fv.Len(); i++ {
			if reflect.DeepEqual(b, fv.Index(i).Interface()) {
				fv.Set(pull(fv, i, fv.Index(i).Type()))
				break inner
			}
		}
	}
	return nil
}

// Swap - swaps the elements at indexes `i` and `j` in the
// slice stored at `field`
func (d *Document) Swap(field string, i, j int) error {
	rv := reflect.ValueOf(d.self)
	selfRef := rv
	rt := reflect.TypeOf(d.self)
	if selfRef.Kind() == reflect.Pointer {
		selfRef = selfRef.Elem()
		rt = rt.Elem()
	}
	if err := checkStruct(selfRef); err != nil {
		return err
	}
	_, origV, err := getNested(field, selfRef)
	if err != nil {
		return err
	}

	origRef := makeSettable(*origV, (*origV).Interface())
	fv := origRef
	if fv.Kind() == reflect.Pointer {
		fv = fv.Elem()
	}
	if err = checkSlice(fv); err != nil {
		return err
	}
	if i >= fv.Len() || j >= fv.Len() {
		return ErrOutOfBounds
	}
	oi := fv.Index(i).Interface()
	oj := fv.Index(j).Interface()

	fv.Index(i).Set(reflect.ValueOf(oj))
	fv.Index(j).Set(reflect.ValueOf(oi))

	return nil
}