package wmo

import (
	"context"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"gogs.mikescher.com/BlackForestBytes/goext/exerr"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
)

func (c *Coll[TData]) Find(ctx context.Context, filter bson.M, opts ...*options.FindOptions) ([]TData, error) {

	pipeline := mongo.Pipeline{}
	pipeline = append(pipeline, bson.D{{Key: "$match", Value: filter}})

	for _, opt := range opts {
		if opt != nil && opt.Sort != nil {
			pipeline = append(pipeline, bson.D{{Key: "$sort", Value: opt.Sort}})
		}
	}

	for _, opt := range opts {
		if opt != nil && opt.Skip != nil {
			pipeline = append(pipeline, bson.D{{Key: "$skip", Value: *opt.Skip}})
		}
	}

	for _, opt := range opts {
		if opt != nil && opt.Limit != nil {
			pipeline = append(pipeline, bson.D{{Key: "$limit", Value: *opt.Limit}})
		}
	}

	for _, ppl := range c.extraModPipeline {
		pipeline = langext.ArrConcat(pipeline, ppl(ctx))
	}

	if c.needsDoubleSort(ctx) {
		for _, opt := range opts {
			if opt != nil && opt.Sort != nil {
				pipeline = append(pipeline, bson.D{{Key: "$sort", Value: opt.Sort}})
			}
		}
	}

	for _, opt := range opts {
		if opt != nil && opt.Projection != nil {
			pipeline = append(pipeline, bson.D{{Key: "$project", Value: opt.Projection}})
		}
	}

	convOpts := make([]*options.AggregateOptions, 0, len(opts))
	for _, v := range opts {
		vConv, err := convertFindOpt(v)
		if err != nil {
			return nil, exerr.Wrap(err, "mongo-aggregation failed").Any("pipeline", pipeline).Str("collection", c.Name()).Build()
		}
		convOpts = append(convOpts, vConv)
	}

	cursor, err := c.coll.Aggregate(ctx, pipeline, convOpts...)
	if err != nil {
		return nil, exerr.Wrap(err, "mongo-aggregation failed").Any("pipeline", pipeline).Str("collection", c.Name()).Build()
	}

	defer func() { _ = cursor.Close(ctx) }()

	res, err := c.decodeAll(ctx, cursor)
	if err != nil {
		return nil, exerr.Wrap(err, "failed to decode values").Build()
	}

	return res, nil
}

// converts FindOptions to AggregateOptions
func convertFindOpt(v *options.FindOptions) (*options.AggregateOptions, error) {
	if v == nil {
		return nil, nil
	}

	r := options.Aggregate()

	if v.AllowDiskUse != nil {
		r.SetAllowDiskUse(*v.AllowDiskUse)
	}
	if v.AllowPartialResults != nil {
		return nil, exerr.New(exerr.TypeMongoInvalidOpt, "Invalid option 'AllowPartialResults' (cannot convert to AggregateOptions)").Build()
	}
	if v.BatchSize != nil {
		r.SetBatchSize(*v.BatchSize)
	}
	if v.Collation != nil {
		r.SetCollation(v.Collation)
	}
	if v.Comment != nil {
		r.SetComment(*v.Comment)
	}
	if v.CursorType != nil {
		return nil, exerr.New(exerr.TypeMongoInvalidOpt, "Invalid option 'CursorType' (cannot convert to AggregateOptions)").Build()
	}
	if v.Hint != nil {
		r.SetHint(v.Hint)
	}
	if v.Max != nil {
		return nil, exerr.New(exerr.TypeMongoInvalidOpt, "Invalid option 'Max' (cannot convert to AggregateOptions)").Build()
	}
	if v.MaxAwaitTime != nil {
		r.SetMaxAwaitTime(*v.MaxAwaitTime)
	}
	if v.MaxTime != nil {
		r.SetMaxTime(*v.MaxTime)
	}
	if v.Min != nil {
		return nil, exerr.New(exerr.TypeMongoInvalidOpt, "Invalid option 'Min' (cannot convert to AggregateOptions)").Build()
	}
	if v.NoCursorTimeout != nil {
		return nil, exerr.New(exerr.TypeMongoInvalidOpt, "Invalid option 'NoCursorTimeout' (cannot convert to AggregateOptions)").Build()
	}
	if v.OplogReplay != nil {
		return nil, exerr.New(exerr.TypeMongoInvalidOpt, "Invalid option 'OplogReplay' (cannot convert to AggregateOptions)").Build()
	}
	if v.ReturnKey != nil {
		return nil, exerr.New(exerr.TypeMongoInvalidOpt, "Invalid option 'ReturnKey' (cannot convert to AggregateOptions)").Build()
	}
	if v.ShowRecordID != nil {
		return nil, exerr.New(exerr.TypeMongoInvalidOpt, "Invalid option 'ShowRecordID' (cannot convert to AggregateOptions)").Build()
	}
	if v.Snapshot != nil {
		return nil, exerr.New(exerr.TypeMongoInvalidOpt, "Invalid option 'Snapshot' (cannot convert to AggregateOptions)").Build()
	}
	if v.Let != nil {
		r.SetLet(v.Let)
	}

	return r, nil
}