package region

import (
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"

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

const nodeSpecPageTokenVersion = 1

type nodeSpecSortKey struct {
	CPU         int64  `json:"cpu"`
	MemoryGi    int64  `json:"memory_gi"`
	NodeSpecKey string `json:"node_spec_key"`
}

type nodeSpecPageToken struct {
	Version            int             `json:"version"`
	RequestFingerprint string          `json:"request_fingerprint"`
	LastKey            nodeSpecSortKey `json:"last_key"`
}

type nodeSpecRequestFingerprint struct {
	Version        int    `json:"version"`
	OrganizationID uint64 `json:"organization_id"`
	ProviderID     int32  `json:"provider_id"`
	RegionID       int32  `json:"region_id"`
	ComponentType  int32  `json:"component_type"`
}

func parseNodeSpecPageToken(
	request *globalngv1.ListNodeSpecsRequest,
) (*nodeSpecSortKey, 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, invalidNodeSpecPageToken()
	}

	var token nodeSpecPageToken
	if err := json.Unmarshal(raw, &token); err != nil {
		return nil, invalidNodeSpecPageToken()
	}
	if token.Version != nodeSpecPageTokenVersion ||
		token.LastKey.CPU <= 0 ||
		token.LastKey.MemoryGi <= 0 ||
		token.LastKey.NodeSpecKey == "" {
		return nil, invalidNodeSpecPageToken()
	}

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

	return &token.LastKey, nil
}

func buildNodeSpecPageToken(
	request *globalngv1.ListNodeSpecsRequest,
	lastKey nodeSpecSortKey,
) (string, bizerr.BizError) {
	fingerprint, err := nodeSpecFingerprint(request)
	if err != nil {
		return "", bizerr.WrapError(err, "calculate node spec request fingerprint")
	}

	raw, err := json.Marshal(nodeSpecPageToken{
		Version:            nodeSpecPageTokenVersion,
		RequestFingerprint: fingerprint,
		LastKey:            lastKey,
	})
	if err != nil {
		return "", bizerr.WrapError(err, "marshal node spec page token")
	}

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

func nodeSpecFingerprint(request *globalngv1.ListNodeSpecsRequest) (string, error) {
	raw, err := json.Marshal(nodeSpecRequestFingerprint{
		Version:        nodeSpecPageTokenVersion,
		OrganizationID: request.GetOrganizationId(),
		ProviderID:     int32(request.GetCloudProviderId()),
		RegionID:       int32(request.GetRegionId()),
		ComponentType:  int32(request.GetComponentType()),
	})
	if err != nil {
		return "", err
	}

	sum := sha256.Sum256(raw)
	return hex.EncodeToString(sum[:]), nil
}

func invalidNodeSpecPageToken() bizerr.BizError {
	return bizerr.New(bizerr.ErrCodeParams, "invalid node spec page token")
}
