package orm

import (
	"encoding/json"
	"fmt"
	"go.mongodb.org/mongo-driver/v2/bson"
	"log"
	"reflect"
	"strings"
	"time"
)

type Document struct {
	// Created time. updated/added automatically.
	Created time.Time `bson:"createdAt" json:"createdAt" tstype:"Date"`
	// Modified time. updated/added automatically.
	Modified        time.Time       `bson:"updatedAt" json:"updatedAt" tstype:"Date"`
	model           *Model          `bson:"-"`
	exists          bool            `bson:"-"`
	self            any             `bson:"-"`
	raw             any             `bson:"-"`
	populatedFields map[string]bool `bson:"-"`
}

func (d *Document) UnmarshalJSON(bytes []byte) error {
	var fiv interface{}
	if err := json.Unmarshal(bytes, &fiv); err != nil {
		return err
	}
	var err error
	switch fiv.(type) {
	case []interface{}:
		tmpV := make(bson.A, 0)
		err = json.Unmarshal(bytes, &tmpV)
		typ := reflect.SliceOf(d.model.Type)
		//d.SetSelf()
		var arr []interface{}
		reified := rerere(tmpV, typ, true)
		if ta, ok := reified.(bson.A); ok {
			arr = []interface{}(ta)
		} else {
			arr = reified.([]interface{})
		}
		fmt.Println(len(arr))
		break
	case map[string]interface{}:
		tmpV := make(bson.M)
		err = json.Unmarshal(bytes, &tmpV)
		typ := reflect.PointerTo(d.model.Type)
		self := reflect.ValueOf(d.self)
		nself := reflect.NewAt(typ.Elem(), self.UnsafePointer())
		reified := rerere(tmpV, typ, true)
		nself.Elem().Set(reflect.ValueOf(reified).Elem())
		d.self = nself.Interface()
		break
	}
	return err
}

func (d *Document) MarshalJSON() ([]byte, error) {
	v := serializeIDs((d).self, true, d.populatedFields, "")
	return json.Marshal(v)
}

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
	Populate(fields ...string)
	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)
	markPopulated(field string)
	markDepopulated(field string)
	newPopulationMap()
	getRaw() any
	setRaw(raw any)
}

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
}

// SetSelf - don't call this lol
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, false, d.populatedFields, "")
}

// 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
}

func (d *Document) Populate(fields ...string) {
	_, cm, _ := ModelRegistry.HasByName(d.model.typeName)

	if cm != nil {
		rawDoc := d.raw
		for _, field := range fields {
			// 0 = fieldname, 1 = typename, 2 = bson name

			r, _ := readFields(field, cm)

			if r.exists {
				// get self
				// get ptr
				// find
				// unmarshal...
				htt := r.HydratedType
				if htt.Kind() == reflect.Pointer || htt.Kind() == reflect.Slice {
					htt = htt.Elem()
				}
				if strings.HasSuffix(field, ".") || strings.HasPrefix(field, ".") {
					log.Printf("WARN: invalid field name passed to Populate(). skipping...\n")
					continue
				}

				tto := r.HydratedType
				if tto.Kind() == reflect.Pointer || tto.Kind() == reflect.Slice {
					tto = tto.Elem()
				}
				_, refColl, _ := ModelRegistry.HasByName(tto.Name())
				var tmp1 interface{}
				asIDocument, docOk := d.self.(IDocument)
				if docOk {
					asIDocument.markPopulated(field)
				}

				tmp1 = populate(r, refColl.collection, rawDoc, field, reflect.ValueOf(d.self).Interface())
				d.self = tmp1
			}
		}
	}
}