package orm

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

type Query struct {
	collection *mongo.Collection
	op         string
	model      *Model
	done       bool
	rawDoc     any
	doc        any
}

const (
	OP_FIND_ONE   = "findOne"
	OP_FIND_PAGED = "findPaged"
	OP_FIND_ALL   = "findAll"
	OP_FIND       = "find"
)

func populate(r Reference, rcoll string, rawDoc interface{}, d string, src interface{}) any {
	rt := reflect.TypeOf(src)
	rv := reflect.ValueOf(src)
	srt := rt
	if srt.Kind() == reflect.Pointer {
		srt = rt.Elem()
	}
	if rv.Kind() != reflect.Pointer {
		rv = reflect.New(rt)
		rv.Elem().Set(reflect.ValueOf(src))
	}

	var fieldsMap [3]string
	type bsonWhat struct {
		What string
	}
	var w bsonWhat
	switch rawDoc.(type) {
	case bson.A:
		w.What = "A"
	case bson.D:
		w.What = "D"
	case bson.M:
		w.What = "M"
	default:
		w.What = "-"
	}
	var next string
	if len(strings.Split(d, ".")) > 1 {
		next = strings.Join(strings.Split(d, ".")[1:], ".")
		d = strings.Split(d, ".")[0]
	} else {
		next = d
	}
	var toReturn interface{}
	switch w.What {
	case "A":
		rvs := reflect.MakeSlice(rt, 0, 0)
		var rahh reflect.Value
		if rv.Kind() == reflect.Ptr {
			rahh = rv.Elem()
		} else {
			rahh = rv
		}
		for i, el := range rawDoc.(bson.A) {
			it := rahh.Index(i)
			popped := populate(r, rcoll, el, next, it.Interface())
			poppedVal := reflect.ValueOf(popped)
			if poppedVal.Kind() == reflect.Pointer {
				rvs = reflect.Append(rvs, poppedVal.Elem())
			} else {
				rvs = reflect.Append(rvs, poppedVal)
			}
		}
		if rv.CanSet() {
			rv.Set(rvs)
		} else if rv.Kind() == reflect.Pointer {
			rv.Elem().Set(rvs)
		} else {
			src = rvs.Interface()
			toReturn = src
		}
	case "D":
		loc := rawDoc.(bson.D)
		nrd := bson.M{}
		for _, el := range loc {
			nrd[el.Key] = el.Value
		}
		rawDoc = nrd
		fallthrough
	case "M":
		dd := rawDoc.(bson.M)
		var sf reflect.Value
		var rsf reflect.Value
		if rv.Kind() == reflect.Pointer {
			sf = rv.Elem().FieldByName(d)
		} else {
			sf = rv.FieldByName(d)
		}
		if rv.Kind() == reflect.Pointer {
			rsf = rv.Elem().FieldByName(d)
		} else {
			rsf = rv.FieldByName(d)
		}

		var ff reflect.StructField
		var ok bool
		if rt.Kind() == reflect.Pointer {
			ff, ok = rt.Elem().FieldByName(d)
		} else {
			ff, ok = rt.FieldByName(d)
		}

		if ok {
			tag, err := structtag.Parse(string(ff.Tag))
			if err == nil {
				val, err2 := tag.Get("bson")
				if err2 == nil {
					fttt := ff.Type
					if fttt.Kind() == reflect.Pointer || fttt.Kind() == reflect.Slice {
						fttt = fttt.Elem()
					}
					fieldsMap = [3]string{d, fttt.Name(), val.Name}
				}
			}
		}
		intermediate := populate(r, rcoll, dd[fieldsMap[2]], next, sf.Interface())
		if rsf.CanSet() {
			ival := reflect.ValueOf(intermediate)
			if ival.Kind() != reflect.Pointer && rsf.Kind() == reflect.Pointer {
				rsf.Set(ival.Elem())
			} else if ival.Kind() == reflect.Pointer && rsf.Kind() != reflect.Pointer {
				rsf.Set(ival.Elem())
			} else {
				rsf.Set(ival)
			}
		} else {
			src = intermediate
		}
	default:
		tto := r.HydratedType
		if tto.Kind() == reflect.Pointer || tto.Kind() == reflect.Slice {
			tto = tto.Elem()
		}

		rawt := ModelRegistry.new_(tto.Name())
		t := rawt.(IDocument)
		q := bson.M{"_id": rawDoc}
		reso := DB.Collection(rcoll).FindOne(context.TODO(), q)
		reso.Decode(t)
		hatred := rv
		if hatred.Kind() == reflect.Pointer {
			hatred = hatred.Elem()
		}
		if hatred.CanSet() {
			if reflect.ValueOf(t).Kind() == reflect.Pointer {
				fmt.Println(reflect.ValueOf(t).Elem().Type().String())
				fmt.Println(rv.Type().String())
				hatred.Set(reflect.ValueOf(t).Elem())
			} else {
				hatred.Set(reflect.ValueOf(t))
			}
		} else {
			src = t
			toReturn = src
		}
	}

	if toReturn == nil {
		return rv.Interface()
	}
	return src
}

// LoadFile - loads the contents of one or more files
// stored in gridFS into the fields named by `fields`.
//
// gridFS fields can be either a `string` or `[]byte`, and are
// tagged with `gridfs:"BUCKET,FILE_FORMAT`
// where:
//   - `BUCKET` is the name of the bucket where the files are stored
//   - `FILE_FORMAT` is any valid go template string that resolves to
//     the unique file name.
//     all exported values and methods present in the surrounding
//     struct can be used in this template.
func (q *Query) LoadFile(fields ...string) *Query {
	_, cm, _ := ModelRegistry.HasByName(q.model.typeName)
	if cm != nil {
		for _, field := range fields {
			var r gridFSReference
			hasAnnotated := false
			for k2, v := range cm.gridFSReferences {
				if strings.HasPrefix(k2, field) {
					r = v
					hasAnnotated = true
					break
				}
			}
			if hasAnnotated {
				q.doc = gridFsLoad(q.doc, r, field)
			}
		}
	}
	return q
}

// Populate populates document references via reflection
func (q *Query) Populate(fields ...string) *Query {
	_, cm, _ := ModelRegistry.HasByName(q.model.typeName)

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

			var r Reference

			for k2, v := range cm.references {
				if strings.HasPrefix(k2, field) {
					r = v
					break
				}
			}

			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{}
				if arr, ok := rawDoc.(bson.A); ok {
					typ := reflect.PointerTo(cm.Type)
					slic := reflect.New(reflect.SliceOf(typ))
					for i, val2 := range arr {
						ref := reflect.ValueOf(q.doc)
						if ref.Kind() == reflect.Pointer {
							ref = ref.Elem()
						}
						src := ref.Index(i).Interface()
						inter := populate(r, refColl.collection, val2, field, src)
						if reflect.ValueOf(inter).Kind() == reflect.Pointer {
							slic.Elem().Set(reflect.Append(slic.Elem(), reflect.ValueOf(inter)))
						} else {
							slic.Elem().Set(reflect.Append(slic, reflect.ValueOf(inter)))
						}
					}
					tmp1 = slic.Interface()
				} else {
					tmp1 = populate(r, refColl.collection, rawDoc, field, reflect.ValueOf(q.doc).Interface())
				}
				q.doc = tmp1
			}
		}
	}
	return q
}
func (q *Query) reOrganize() {
	var trvo reflect.Value
	if arr, ok := q.rawDoc.(bson.A); ok {
		typ := ModelRegistry[q.model.typeName].Type
		if typ.Kind() != reflect.Pointer {
			typ = reflect.PointerTo(typ)
		}
		slic := reflect.New(reflect.SliceOf(typ))
		for _, v2 := range arr {
			inter := reflect.ValueOf(rerere(v2, typ))
			slic.Elem().Set(reflect.Append(slic.Elem(), inter))
		}
		trvo = slic.Elem()
	} else {
		trvo = reflect.ValueOf(rerere(q.rawDoc, reflect.TypeOf(q.doc)))
	}

	resV := reflect.ValueOf(q.doc)
	for {
		if resV.Kind() == reflect.Pointer {
			if resV.Elem().Kind() == reflect.Slice {
				resV = resV.Elem()
			} else {
				break
			}
		} else {
			break
		}
	}
	if resV.CanSet() {
		resV.Set(trvo)
	} else {
		resV.Elem().Set(trvo.Elem())
	}
}

func rerere(input interface{}, resType reflect.Type) interface{} {
	t := reflect.TypeOf(input)
	v := reflect.ValueOf(input)
	if input == nil {
		return nil
	}
	if v.Type().Kind() == reflect.Pointer {
		v = v.Elem()
	}
	if t.Kind() == reflect.Pointer {
		t = t.Elem()
	}

	if resType.Kind() == reflect.Pointer {
		resType = resType.Elem()
	}
	resV := reflect.New(resType)
	var rve = resV
	if rve.Kind() == reflect.Pointer {
		rve = resV.Elem()
	}

	if d, isD := v.Interface().(bson.D); isD {
		m := bson.M{}
		for _, el := range d {
			m[el.Key] = el.Value
		}
		input = m
		v = reflect.ValueOf(input)
	}

	switch resType.Kind() {
	case reflect.Struct:
		shouldBreak := false
		mipmap, ok := v.Interface().(bson.M)
		if ok {
			for i := 0; i < resType.NumField(); i++ {
				ft := resType.Field(i)
				fv := rve.Field(i)
				if ft.Anonymous {
					fv.Set(handleAnon(input, ft.Type, fv))
					continue
				}
				tags, err := structtag.Parse(string(ft.Tag))
				panik(err)
				btag, err := tags.Get("bson")
				if err != nil {
					continue
				}
				if btag.Name == "-" {
					continue
				}
				intermediate := mipmap[btag.Name]
				_, err = tags.Get("ref")
				if err != nil {
					tmp := rerere(intermediate, ft.Type)
					fuck := reflect.ValueOf(tmp)
					if tmp != nil {
						if fuck.Type().Kind() == reflect.Pointer {
							fuck = fuck.Elem()
						}
						fv.Set(fuck)
					} else {
						fv.Set(reflect.Zero(ft.Type))
					}
					shouldBreak = true
				} else {
					tt := ft.Type
					if tt.Kind() == reflect.Pointer {
						tt = tt.Elem()
					}
					tmp := rerere(intermediate, ft.Type)
					if tmp != nil {
						if reflect.ValueOf(tmp).Kind() == reflect.Pointer && fv.Kind() != reflect.Pointer {
							fv.Set(reflect.ValueOf(tmp).Elem())
						} else {
							fv.Set(reflect.ValueOf(tmp))
						}
					} else {
						fv.Set(reflect.Zero(ft.Type))
					}
				}
			}
			if shouldBreak {
				// break
			}
		} else {
			nunu := ModelRegistry.new_(resType.Name())
			ider, ok := nunu.(HasID)
			if ok {
				ider.SetId(v.Interface())
				if reflect.ValueOf(ider).Kind() == reflect.Pointer {
					nunu = reflect.ValueOf(ider).Elem().Interface()
				}
				rve.Set(reflect.ValueOf(nunu))
			}
		}
	case reflect.Slice:
		arr := v.Interface().(bson.A)
		for _, it := range arr {
			if it != nil {
				tmp := reflect.ValueOf(rerere(it, rve.Type().Elem()))
				if tmp.Kind() == reflect.Pointer {
					tmp = tmp.Elem()
				}
				rve.Set(reflect.Append(rve, tmp))
			}
		}
	default:
		if resType.AssignableTo(v.Type()) {
			rve.Set(reflect.ValueOf(input))
		} else {
			switch rve.Interface().(type) {
			case int, int32, int64, uint, uint32, uint64:
				rve.Set(reflect.ValueOf(coerceInt(v, rve)))
			}
		}
	}

	return resV.Interface()
}

func handleAnon(raw interface{}, rtype reflect.Type, rval reflect.Value) reflect.Value {
	f := rval
	g := rtype
	if rtype.Kind() == reflect.Pointer {
		g = rtype.Elem()
	}
	if !f.CanSet() {
		f = reflect.New(g)
		f.Elem().Set(rval)
	}
	if rtype.Kind() != reflect.Struct {
		return rval
	}

	for i := 0; i < rtype.NumField(); i++ {
		typeField := rtype.Field(i)
		valueField := f.Field(i)
		tags, err := structtag.Parse(string(typeField.Tag))
		if !typeField.IsExported() {
			continue
		}
		if err == nil {
			var btag *structtag.Tag
			btag, err = tags.Get("bson")
			if err != nil {
				continue
			}
			if btag.Name == "-" {
				continue
			}
			amap, ok := raw.(bson.M)
			if ok {
				fval := amap[btag.Name]
				if reflect.TypeOf(fval) == reflect.TypeFor[bson.DateTime]() {
					fval = time.UnixMilli(int64(fval.(bson.DateTime)))
				}
				if valueField.Kind() == reflect.Pointer {
					valueField.Elem().Set(reflect.ValueOf(fval))
				} else {
					valueField.Set(reflect.ValueOf(fval))
				}
			}
		}
	}
	return f
}

// JSON - marshals this Query's results into json format
func (q *Query) JSON() (string, error) {
	res, err := json.MarshalIndent(q.doc, "", "\t")
	if err != nil {
		return "", err
	}
	return string(res[:]), nil
}

// Exec - executes the query and puts its results into the
// provided argument.
//
// Will panic if called more than once on the same Query instance.
func (q *Query) Exec(result interface{}) {
	if q.done {
		panic("Exec() has already been called!")
	}
	doc := reflect.ValueOf(q.doc)
	if doc.Elem().Kind() == reflect.Slice {
		for i := 0; i < doc.Elem().Len(); i++ {
			cur := doc.Elem().Index(i)
			imodel, ok := cur.Interface().(IDocument)
			if ok {
				imodel.setExists(true)
				doc.Elem().Index(i).Set(reflect.ValueOf(imodel))
			}
		}
	}
	reflect.ValueOf(result).Elem().Set(reflect.ValueOf(q.doc).Elem())
	q.done = true
}