package utils

import (
	"strconv"

	"github.com/tidbcloud/dataflow-service/app/config"
	bizerr_wrapper "github.com/tidbcloud/dataflow-service/pkg/bizerr-wrapper"
	"github.com/tidbcloud/pkgv2/bizerr"
	"go.einride.tech/aip/pagination"
	"google.golang.org/protobuf/proto"
)

const (
	defaultMaxPageSize     = 100
	defaultPageSize        = 10
	defaultFirstPageNumber = 1
)

type AipPager struct {
	MaxPageSize     uint32
	DefaultPageSize uint32
	pageSize        uint32
	pageToken       pagination.PageToken
	request         pagination.Request
}

func NewAipPager() *AipPager {
	return &AipPager{
		MaxPageSize:     defaultMaxPageSize,
		DefaultPageSize: defaultPageSize,
	}
}

func (p *AipPager) Normalize(r pagination.Request) bizerr.BizError {
	pageSize := r.GetPageSize()
	switch {
	case pageSize < 0:
		return bizerr.New(config.ErrCodeNegativePageSize, "page size is negative")
	case pageSize == 0:
		p.pageSize = p.DefaultPageSize
	case pageSize > int32(p.MaxPageSize):
		p.pageSize = p.MaxPageSize
	default:
		p.pageSize = uint32(pageSize)
	}

	pageToken, err := pagination.ParsePageToken(r)
	if err != nil {
		return bizerr.New(config.ErrCodeInvalidPageToken, "invalid page token")
	}
	p.pageToken = pageToken
	p.request = r

	return nil
}

func (p *AipPager) PageSize() uint32 {
	return p.pageSize
}

func (p *AipPager) Offset() uint32 {
	return uint32(p.pageToken.Offset)
}

func (p *AipPager) NextPageToken(total uint32) string {
	if p.Offset()+p.PageSize() >= total {
		return ""
	}
	return p.pageToken.Next(p.request).String()
}

type PageNumberPager struct {
	MaxPageSize      uint32
	DefaultPageSize  uint32
	DefaultFirstPage uint32
	page             uint32
	pageSize         uint32
}

func NewPagerNumberPager() *PageNumberPager {
	return &PageNumberPager{
		MaxPageSize:      defaultMaxPageSize,
		DefaultPageSize:  defaultPageSize,
		DefaultFirstPage: defaultFirstPageNumber,
	}
}

type request1 = pagination.Request

type request2 interface {
	proto.Message
	GetPageToken() uint32
	GetPageSize() uint32
}

type request3 interface {
	proto.Message
	GetPageToken() string
	GetPageSize() uint32
}

func (p *PageNumberPager) Normalize(r interface{}) bizerr.BizError {
	switch v := r.(type) {
	case request1:
		return p.normalize1(v)
	case request2:
		return p.normalize2(v)
	case request3:
		return p.normalize3(v)
	default:
		return bizerr_wrapper.NewInternalErr("unsupported request type")
	}
}

func (p *PageNumberPager) normalize1(r request1) bizerr.BizError {
	pageSize := r.GetPageSize()
	switch {
	case pageSize < 0:
		return bizerr.New(config.ErrCodeNegativePageSize, "page size is negative")
	case pageSize == 0:
		p.pageSize = p.DefaultPageSize
	case pageSize > int32(p.MaxPageSize):
		p.pageSize = p.MaxPageSize
	default:
		p.pageSize = uint32(pageSize)
	}

	if pageToken := r.GetPageToken(); pageToken == "" {
		p.page = p.DefaultFirstPage
	} else {
		token, err := strconv.ParseUint(pageToken, 10, 32)
		if err != nil {
			return bizerr.New(config.ErrCodeInvalidPageToken, "invalid page token")
		}
		p.page = uint32(token)
	}
	return nil
}

func (p *PageNumberPager) normalize2(r request2) bizerr.BizError {
	pageSize := r.GetPageSize()
	switch {
	case pageSize == 0:
		p.pageSize = p.DefaultPageSize
	case pageSize > p.MaxPageSize:
		p.pageSize = p.MaxPageSize
	default:
		p.pageSize = pageSize
	}

	if pageToken := r.GetPageToken(); pageToken == 0 {
		p.page = p.DefaultFirstPage
	} else {
		p.page = pageToken
	}
	return nil
}

func (p *PageNumberPager) normalize3(r request3) bizerr.BizError {
	pageSize := r.GetPageSize()
	switch {
	case pageSize == 0:
		p.pageSize = p.DefaultPageSize
	case pageSize > p.MaxPageSize:
		p.pageSize = p.MaxPageSize
	default:
		p.pageSize = pageSize
	}

	if pageToken := r.GetPageToken(); pageToken == "" {
		p.page = p.DefaultFirstPage
	} else {
		token, err := strconv.ParseUint(pageToken, 10, 32)
		if err != nil {
			return bizerr.New(config.ErrCodeInvalidPageToken, "invalid page token")
		}
		p.page = uint32(token)
	}
	return nil
}

func (p *PageNumberPager) Page() uint32 {
	return p.page
}

func (p *PageNumberPager) PageSize() uint32 {
	return p.pageSize
}

func (p *PageNumberPager) Offset() uint32 {
	return (p.page - 1) * p.pageSize
}

func (p *PageNumberPager) NextPageToken(total uint32) string {
	if p.Offset()+p.PageSize() >= total {
		return ""
	}
	return strconv.FormatUint(uint64(p.page+1), 10)
}

// PaginateSlice paginates a slice in memory
// page is 1-based (first page is 1, not 0)
func PaginateSlice[T any](items []T, page, pageSize int64) []T {
	if page <= 0 || pageSize <= 0 || len(items) == 0 {
		return items
	}

	total := int64(len(items))
	start := (page - 1) * pageSize

	if start >= total {
		return []T{}
	}

	end := start + pageSize
	if end > total {
		end = total
	}

	return items[start:end]
}
