package common

import (
	"context"
	"encoding/base64"
	"fmt"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/log"
	"go.uber.org/zap"
	"math"
	"reflect"
	"strconv"

	"gorm.io/gorm"
)

const (
	DEFAULTPAGESIZE = 100
	DEFAULTPAGE     = 1
)

// Paginator represents the paginated response with Google-style pagination fields.
type Paginator struct {
	PageSize      int32  `json:"page_size"`
	Total         uint64 `json:"total"`
	TotalPages    uint64 `json:"totalPages"`
	NextPageToken string `json:"nextPageToken"`
	Data          any    `json:"data,omitempty"`
}

// Paginator represents the paginated response with Google-style pagination fields.
type TableQuerySet struct {
	DB        *gorm.DB
	CountExpr string
}

// NewPaginator creates a Paginator with encoded page token
func NewPaginator(pageSize int32, total uint64, nextPageToken string, data interface{}) *Paginator {
	totalPages := uint64(math.Ceil(float64(total) / float64(pageSize)))
	return &Paginator{
		PageSize:      pageSize,
		Total:         total,
		TotalPages:    totalPages,
		NextPageToken: nextPageToken,
		Data:          data,
	}
}

func PaginationSliceInMemory(ctx context.Context, pageParams PageParams, modelArrayPointer any) (*Paginator, error) {
	// 1. Check if modelArrayPointer is a pointer to a slice
	if !isPointerToSlice(modelArrayPointer) {
		return nil, fmt.Errorf("modelArrayPointer must be a pointer to a slice")
	}

	// 2. Get the slice value from modelArrayPointer
	sliceValue := reflect.ValueOf(modelArrayPointer).Elem()
	total := sliceValue.Len()

	// 3. Calculate paginationCalculate the start and end index
	offset, pageSize := calculatePagination(pageParams)
	if total == 0 || offset >= total {
		sliceValue.Set(reflect.MakeSlice(sliceValue.Type(), 0, 0))
		return NewPaginator(pageSize, 0, "", modelArrayPointer), nil
	}
	end := offset + int(pageSize)
	if end > total {
		end = total
	}

	// 4.  Paginated slice
	paginatedData := sliceValue.Slice(offset, end)
	sliceValue.Set(paginatedData)

	// 5. Generate the next page token
	nextPageToken := generateNextPageToken(offset, int(pageSize), total)

	// Return pagination result
	pagination := NewPaginator(pageSize, uint64(total), nextPageToken, modelArrayPointer)

	log.Info(ctx, "ListEntitiesWithPaginationInMemory", zap.Any("TotalPages", pagination.TotalPages), zap.Any("Total", pagination.Total))
	return pagination, nil

}

// encodePageToken encodes the current page offset and size for the next page token
func encodePageToken(offset int) string {
	return base64.StdEncoding.EncodeToString([]byte(strconv.Itoa(offset)))
}

// decodePageToken decodes the page token to get the offset value
func decodePageToken(pageToken string) (int, error) {
	decoded, err := base64.StdEncoding.DecodeString(pageToken)
	if err != nil {
		return 0, fmt.Errorf("invalid page token")
	}
	offset, err := strconv.Atoi(string(decoded))
	if err != nil {
		return 0, fmt.Errorf("invalid page token")
	}
	return offset, nil
}
