package builderimp

import (
	"reflect"

	"github.com/tech/sendico/pkg/db/repository/builder"
	"github.com/tech/sendico/pkg/db/storable"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
)

type QueryImp struct {
	filter bson.D
	sort   bson.D
	limit  *int64
	offset *int64
}

func (b *QueryImp) Filter(field builder.Field, value any) builder.Query {
	b.filter = append(b.filter, bson.E{Key: field.Build(), Value: value})
	return b
}

func (b *QueryImp) And(filters ...builder.Query) builder.Query {
	andFilters := bson.A{}
	for _, f := range filters {
		andFilters = append(andFilters, f.BuildQuery())
	}
	b.filter = append(b.filter, bson.E{Key: string(builder.And), Value: andFilters})
	return b
}

func (b *QueryImp) Or(filters ...builder.Query) builder.Query {
	orFilters := bson.A{}
	for _, f := range filters {
		orFilters = append(orFilters, f.BuildQuery())
	}
	b.filter = append(b.filter, bson.E{Key: string(builder.Or), Value: orFilters})
	return b
}

func (b *QueryImp) Comparison(field builder.Field, operator builder.MongoOperation, value any) builder.Query {
	b.filter = append(b.filter, bson.E{Key: field.Build(), Value: bson.M{string(operator): value}})
	return b
}

func (b *QueryImp) Expression(value builder.Expression) builder.Query {
	b.filter = append(b.filter, bson.E{Key: string(builder.Expr), Value: value.Build()})
	return b
}

func (b *QueryImp) RegEx(field builder.Field, pattern, options string) builder.Query {
	b.filter = append(b.filter, bson.E{Key: field.Build(), Value: bson.Regex{Pattern: pattern, Options: options}})
	return b
}

func (b *QueryImp) opIn(field builder.Field, op builder.MongoOperation, values ...any) builder.Query {
	var flattenedValues []any

	for _, v := range values {
		switch reflect.TypeOf(v).Kind() {
		case reflect.Slice:
			slice := reflect.ValueOf(v)
			for i := range slice.Len() {
				flattenedValues = append(flattenedValues, slice.Index(i).Interface())
			}
		default:
			flattenedValues = append(flattenedValues, v)
		}
	}

	b.filter = append(b.filter, bson.E{Key: field.Build(), Value: bson.M{string(op): flattenedValues}})
	return b
}

func (b *QueryImp) NotIn(field builder.Field, values ...any) builder.Query {
	return b.opIn(field, builder.NotIn, values...)
}

func (b *QueryImp) In(field builder.Field, values ...any) builder.Query {
	return b.opIn(field, builder.In, values...)
}

func (b *QueryImp) Archived(isArchived *bool) builder.Query {
	if isArchived == nil {
		return b
	}
	return b.And(NewQueryImp().Filter(NewFieldImp(storable.IsArchivedField), *isArchived))
}

func (b *QueryImp) Sort(field builder.Field, ascending bool) builder.Query {
	order := 1
	if !ascending {
		order = -1
	}
	b.sort = append(b.sort, bson.E{Key: field.Build(), Value: order})
	return b
}

func (b *QueryImp) BuildPipeline() bson.D {
	query := bson.D{}

	if len(b.filter) > 0 {
		query = append(query, bson.E{Key: string(builder.Match), Value: b.filter})
	}

	if len(b.sort) > 0 {
		query = append(query, bson.E{Key: string(builder.Sort), Value: b.sort})
	}

	if b.limit != nil {
		query = append(query, bson.E{Key: string(builder.Limit), Value: *b.limit})
	}

	if b.offset != nil {
		query = append(query, bson.E{Key: string(builder.Skip), Value: *b.offset})
	}

	return query
}

func (b *QueryImp) BuildQuery() bson.D {
	return b.filter
}

func (b *QueryImp) Limit(limit *int64) builder.Query {
	b.limit = limit
	return b
}

func (b *QueryImp) Offset(offset *int64) builder.Query {
	b.offset = offset
	return b
}

func (b *QueryImp) BuildOptions() *options.FindOptionsBuilder {
	opts := options.Find()
	if b.limit != nil {
		opts.SetLimit(*b.limit)
	}
	if b.offset != nil {
		opts.SetSkip(*b.offset)
	}
	if len(b.sort) > 0 {
		opts.SetSort(b.sort)
	}
	return opts
}

func NewQueryImp() builder.Query {
	return &QueryImp{
		filter: bson.D{},
		sort:   bson.D{},
	}
}
