package utils

import (
	"strconv"

	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/serverless-service/internal/errorcode"
	"go.einride.tech/aip/pagination"
	"google.golang.org/protobuf/proto"
)

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

func NewAipPager() *AipPager {
	return &AipPager{
		MaxPageSize:     100,
		DefaultPageSize: 10,
	}
}

func (p *AipPager) Normalize(r pagination.Request) bizerr.BizError {
	pageSize := r.GetPageSize()
	switch {
	case pageSize < 0:
		return bizerr.New(errorcode.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(errorcode.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:      100,
		DefaultPageSize:  10,
		DefaultFirstPage: 1,
	}
}

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 any) 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.New(errorcode.ErrCodeInternalError, "unsupported request page type")
	}
}

func (p *PageNumberPager) normalize1(r request1) bizerr.BizError {
	pageSize := r.GetPageSize()
	switch {
	case pageSize < 0:
		return bizerr.New(errorcode.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 := r.GetPageToken()
	if pageToken == "" {
		p.page = p.DefaultFirstPage
	} else {
		pageToken, err := strconv.ParseUint(pageToken, 10, 32)
		if err != nil {
			return bizerr.New(errorcode.ErrCodeInvalidPageToken, "invalid page token")
		}
		p.page = uint32(pageToken)
	}
	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
	}

	pageToken := r.GetPageToken()
	if 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
	}

	pageToken := r.GetPageToken()
	if pageToken == "" {
		p.page = p.DefaultFirstPage
	} else {
		pageToken, err := strconv.ParseUint(pageToken, 10, 32)
		if err != nil {
			return bizerr.New(errorcode.ErrCodeInvalidPageToken, "invalid page token")
		}
		p.page = uint32(pageToken)
	}
	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)
}
