package adapter

import (
	"errors"
	"slices"

	"github.com/uptrace/bun"
)

func appendODSetClauses(q *bun.InsertQuery) *bun.InsertQuery {
	return q.Set("rank_att = EXCLUDED.rank_att").
		Set("score_att = EXCLUDED.score_att").
		Set("rank_def = EXCLUDED.rank_def").
		Set("score_def = EXCLUDED.score_def").
		Set("rank_sup = EXCLUDED.rank_sup").
		Set("score_sup = EXCLUDED.score_sup").
		Set("rank_total = EXCLUDED.rank_total").
		Set("score_total = EXCLUDED.score_total")
}

func separateListResultAndNext[T any](res []T, limit int) ([]T, T) {
	var next T

	if len(res) > limit {
		next = res[limit]
		res = res[:limit]
	}

	return res, next
}

type sortDirection uint8

var errInvalidSortDirection = errors.New("invalid sort direction")

const (
	sortDirectionASC sortDirection = iota + 1
	sortDirectionDESC
)

func (sd sortDirection) String() string {
	switch sd {
	case sortDirectionASC:
		return "ASC"
	case sortDirectionDESC:
		return "DESC"
	default:
		return "unknown"
	}
}

type cursorPaginationApplierDataElement struct {
	unique    bool
	column    string
	direction sortDirection
	value     any
}

type cursorPaginationApplier struct {
	data []cursorPaginationApplierDataElement
}

var errCursorNoUniqueFieldUsedForSorting = errors.New("cursor - no unique field used for sorting")

func (a cursorPaginationApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
	if len(a.data) == 0 {
		return q
	}

	uniqueIndex := slices.IndexFunc(a.data, func(element cursorPaginationApplierDataElement) bool {
		return element.unique
	})
	if uniqueIndex < 0 {
		return q.Err(errCursorNoUniqueFieldUsedForSorting)
	}

	return q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
		dataLen := len(a.data)

		// based on https://github.com/prisma/prisma/issues/19159#issuecomment-1713389245

		return q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
			for i := 0; i < dataLen; i++ {
				q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
					current := a.data[i]

					for j := 0; j < i; j++ {
						prev := a.data[j]
						q = q.Where("? = ?", bun.Safe(prev.column), prev.value)
					}

					column := bun.Safe(current.column)
					greaterSymbol := bun.Safe(">")
					lessSymbol := bun.Safe("<")

					if i == dataLen-1 {
						greaterSymbol = ">="
						lessSymbol = "<="
					}

					switch current.direction {
					case sortDirectionASC:
						q = q.Where("? ? ?", column, greaterSymbol, current.value)
					case sortDirectionDESC:
						q = q.Where("? ? ?", column, lessSymbol, current.value)
					default:
						return q.Err(errInvalidSortDirection)
					}

					return q
				})
			}

			return q
		})
	})
}
