package resourcepool

import (
	"encoding/base64"
	"encoding/json"
	"hash/crc32"
	"slices"
	"strings"
	"time"

	"golang.org/x/text/cases"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	resourcepoolrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

const resourcePoolListPageTokenVersion = 1

type resourcePoolListPageToken struct {
	Version            int    `json:"version"`
	CreatedAtUnixNanos int64  `json:"created_at_unix_nanos"`
	ResourcePoolID     uint64 `json:"resource_pool_id"`
	RequestFingerprint uint32 `json:"request_fingerprint"`
}

type resourcePoolListFingerprint struct {
	ResourcePoolIDs  []uint64 `json:"resource_pool_ids,omitempty"`
	ResourcePoolName string   `json:"resource_pool_name,omitempty"`
	ProviderIDs      []int32  `json:"provider_ids,omitempty"`
	RegionIDs        []int32  `json:"region_ids,omitempty"`
	OwnerType        int32    `json:"owner_type,omitempty"`
	OwnerIDs         []uint64 `json:"owner_ids,omitempty"`
	CreatedAfter     int64    `json:"created_after,omitempty"`
	CreatedBefore    int64    `json:"created_before,omitempty"`
	ServicePlan      int32    `json:"service_plan,omitempty"`
	LifecycleStates  []int32  `json:"lifecycle_states,omitempty"`
	ShowDeleted      bool     `json:"show_deleted,omitempty"`
	View             int32    `json:"view,omitempty"`
}

func parseResourcePoolListPageToken(
	request *globalngv1.ListResourcePoolsRequest,
) (*resourcepoolrepo.Cursor, bizerr.BizError) {
	if request.GetPageToken() == "" {
		return nil, nil //nolint:nilnil // An empty token is the valid first-page cursor.
	}

	raw, err := base64.RawURLEncoding.DecodeString(request.GetPageToken())
	if err != nil {
		return nil, invalidResourcePoolListPageToken()
	}

	var token resourcePoolListPageToken
	if err := json.Unmarshal(raw, &token); err != nil {
		return nil, invalidResourcePoolListPageToken()
	}
	if token.Version != resourcePoolListPageTokenVersion ||
		token.CreatedAtUnixNanos <= 0 || token.ResourcePoolID == 0 {
		return nil, invalidResourcePoolListPageToken()
	}

	fingerprint, fingerprintErr := resourcePoolListRequestFingerprint(request)
	if fingerprintErr != nil || token.RequestFingerprint != fingerprint {
		return nil, invalidResourcePoolListPageToken()
	}

	return &resourcepoolrepo.Cursor{
		CreatedAt: time.Unix(0, token.CreatedAtUnixNanos).UTC(),
		ID:        token.ResourcePoolID,
	}, nil
}

func buildResourcePoolListPageToken(
	request *globalngv1.ListResourcePoolsRequest,
	cursor resourcepoolrepo.Cursor,
) (string, bizerr.BizError) {
	fingerprint, err := resourcePoolListRequestFingerprint(request)
	if err != nil {
		return "", bizerr.WrapError(err, "calculate resource pool list request fingerprint")
	}

	raw, err := json.Marshal(resourcePoolListPageToken{
		Version:            resourcePoolListPageTokenVersion,
		CreatedAtUnixNanos: cursor.CreatedAt.UTC().UnixNano(),
		ResourcePoolID:     cursor.ID,
		RequestFingerprint: fingerprint,
	})
	if err != nil {
		return "", bizerr.WrapError(err, "marshal resource pool list page token")
	}

	return base64.RawURLEncoding.EncodeToString(raw), nil
}

func resourcePoolListRequestFingerprint(
	request *globalngv1.ListResourcePoolsRequest,
) (uint32, error) {
	resourcePoolIDs := slices.Clone(request.GetResourcePoolIds())
	slices.Sort(resourcePoolIDs)
	resourcePoolIDs = slices.Compact(resourcePoolIDs)
	ownerIDs := slices.Clone(request.GetOwnerIds())
	slices.Sort(ownerIDs)
	ownerIDs = slices.Compact(ownerIDs)

	providerIDs := make([]int32, 0, len(request.GetProviderIds()))
	for _, providerID := range request.GetProviderIds() {
		providerIDs = append(providerIDs, int32(providerID))
	}
	slices.Sort(providerIDs)
	providerIDs = slices.Compact(providerIDs)

	regionIDs := make([]int32, 0, len(request.GetRegionIds()))
	for _, regionID := range request.GetRegionIds() {
		regionIDs = append(regionIDs, int32(regionID))
	}
	slices.Sort(regionIDs)
	regionIDs = slices.Compact(regionIDs)

	lifecycleStates := make([]int32, 0, len(request.GetLifecycleStates()))
	for _, state := range request.GetLifecycleStates() {
		lifecycleStates = append(lifecycleStates, int32(state))
	}
	slices.Sort(lifecycleStates)
	lifecycleStates = slices.Compact(lifecycleStates)

	fingerprint := resourcePoolListFingerprint{
		ResourcePoolIDs:  resourcePoolIDs,
		ResourcePoolName: cases.Fold().String(strings.TrimSpace(request.GetResourcePoolName())),
		ProviderIDs:      providerIDs,
		RegionIDs:        regionIDs,
		OwnerType:        int32(request.GetOwnerType()),
		OwnerIDs:         ownerIDs,
		ServicePlan:      int32(request.GetServicePlan()),
		LifecycleStates:  lifecycleStates,
		ShowDeleted:      request.GetShowDeleted(),
		View:             int32(request.GetView()),
	}
	if request.GetCreatedAfter() != nil {
		fingerprint.CreatedAfter = request.GetCreatedAfter().AsTime().UTC().UnixNano()
	}
	if request.GetCreatedBefore() != nil {
		fingerprint.CreatedBefore = request.GetCreatedBefore().AsTime().UTC().UnixNano()
	}

	raw, err := json.Marshal(fingerprint)
	if err != nil {
		return 0, err
	}
	return crc32.ChecksumIEEE(raw), nil
}

func invalidResourcePoolListPageToken() bizerr.BizError {
	return bizerr.New(bizerr.ErrCodeParams, "invalid resource pool list page token")
}
