package pagination

import (
	"go.einride.tech/aip/pagination"

	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

// FIXME: copy and fix ut from tidb-management-service/pkg/common/pagination.go, refactor all to use this pagination

// Page represents a page of items.
const (
	ListDefaultPageSize = int32(10)
	ListMaxPageSize     = int32(100)
)

type Page struct {
	Offset   int64
	PageSize int64
}

func NormalizePage(page *Page) *Page {
	if page == nil {
		return &Page{
			Offset:   0,
			PageSize: int64(ListDefaultPageSize),
		}
	}

	if page.PageSize < 0 {
		page.PageSize = int64(ListDefaultPageSize)
	}
	if page.PageSize > int64(ListMaxPageSize) {
		page.PageSize = int64(ListMaxPageSize)
	}

	if page.Offset < 0 {
		page.Offset = 0
	}

	return page
}

// LocalPagination handles pagination for a generic slice of items.
// The input should be filtered and sorted before calling this function.
func LocalPagination[T any](
	input []T,
	request pagination.Request,
	defaultPageSize, maxPageSize int32,
) (output []T, nextPageToken string, err error) {
	return RemotePagination(func(page Page) ([]T, error) {
		pageStart := int(page.Offset)
		pageEnd := pageStart + int(page.PageSize)
		var res []T
		switch {
		case pageStart >= len(input):
		case pageEnd >= len(input):
			res = input[pageStart:]
		default:
			res = input[pageStart:pageEnd]
		}
		return res, nil
	}, len(input), request, defaultPageSize, maxPageSize)
}

// RemotePagination handles pagination for a remote fetch function.
// Filter and sort should be done in the remote fetch function.
// Params of remoteFetch:
//   - page: The **already-validated** page offset and page size applied to final results.
//     Final results should be filtered and sorted before applying page.
func RemotePagination[T any](
	remoteFetch func(Page) ([]T, error),
	remoteReturnedTotal int,
	request pagination.Request,
	defaultPageSize, maxPageSize int32,
) (output []T, nextPageToken string, err error) {
	pageSize := request.GetPageSize()
	switch {
	case pageSize < 0:
		return nil, "", bizerr.New(bizerr.ErrCodeParams, "page size is negative")
	case pageSize == 0:
		pageSize = defaultPageSize
	case pageSize > maxPageSize:
		pageSize = maxPageSize
	}

	pageToken, err := pagination.ParsePageToken(request)
	if err != nil {
		return nil, "", bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid page token")
	}

	if pageToken.Offset < 0 {
		return nil, "", bizerr.New(bizerr.ErrCodeParams, "page offset is negative")
	}

	p := Page{
		Offset:   pageToken.Offset,
		PageSize: int64(pageSize),
	}

	output, err = remoteFetch(p)
	if err != nil {
		return nil, "", err
	}

	if p.Offset+p.PageSize < int64(remoteReturnedTotal) {
		pageToken.Offset += int64(pageSize)
		nextPageToken = pageToken.String()
	}

	return output, nextPageToken, nil
}
