package catalogpublication

import (
	"bytes"
	"crypto/sha256"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"strings"
	"unicode/utf8"

	catalogpublicationv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/catalogpublicationv1"
)

const commandDigestDomain = "nexuscrm.catalog-publication-command/v1"

func validateBeginRequest(request BeginRequest) (string, string, error) {
	if !validCommandID(request.CommandID) {
		return "", "", fmt.Errorf("catalog publication: CommandID must be a lowercase UUIDv7")
	}
	if err := validateOpaque("RuntimeAudience", request.RuntimeAudience, 255); err != nil {
		return "", "", err
	}
	if err := validateReference("ExpectedPrior", request.ExpectedPrior); err != nil {
		return "", "", err
	}
	if err := catalogpublicationv1.ValidateBinding(request.Candidate); err != nil {
		return "", "", err
	}
	candidateDigest, err := catalogpublicationv1.BindingDigest(request.Candidate)
	if err != nil {
		return "", "", err
	}
	requestDigest := publicationCommandDigest(request.RuntimeAudience, request.ExpectedPrior, candidateDigest)
	return candidateDigest, requestDigest, nil
}

func publicationCommandDigest(runtimeAudience string, expectedPrior Reference, candidateDigest string) string {
	var canonical bytes.Buffer
	writeCommandString(&canonical, commandDigestDomain)
	writeCommandString(&canonical, runtimeAudience)
	if expectedPrior.Present {
		canonical.WriteByte(1)
		writeCommandString(&canonical, expectedPrior.Digest)
	} else {
		canonical.WriteByte(0)
	}
	writeCommandString(&canonical, candidateDigest)
	digest := sha256.Sum256(canonical.Bytes())
	return "sha256:" + hex.EncodeToString(digest[:])
}

func validateReference(label string, reference Reference) error {
	if !reference.Present {
		if reference.Digest != "" {
			return fmt.Errorf("catalog publication: %s digest must be empty when absent", label)
		}
		return nil
	}
	if err := validateSHA256Digest(reference.Digest); err != nil {
		return fmt.Errorf("catalog publication: %s: %w", label, err)
	}
	return nil
}

func validateSHA256Digest(value string) error {
	if len(value) != 71 || !strings.HasPrefix(value, "sha256:") {
		return fmt.Errorf("digest must use sha256:<64 lowercase hex>")
	}
	for _, character := range value[len("sha256:"):] {
		if (character < '0' || character > '9') && (character < 'a' || character > 'f') {
			return fmt.Errorf("digest must use sha256:<64 lowercase hex>")
		}
	}
	return nil
}

func validateOpaque(label, value string, maximum int) error {
	if value == "" || len(value) > maximum || !utf8.ValidString(value) {
		return fmt.Errorf("catalog publication: %s must be well-formed UTF-8 with 1..%d bytes", label, maximum)
	}
	if value[0] == ' ' || value[len(value)-1] == ' ' {
		return fmt.Errorf("catalog publication: %s must not have surrounding ASCII space", label)
	}
	for _, b := range []byte(value) {
		if b < 0x20 || b == 0x7f {
			return fmt.Errorf("catalog publication: %s must not contain ASCII control or DEL bytes", label)
		}
	}
	return nil
}

func validCommandID(value string) bool {
	if len(value) != 36 || value[8] != '-' || value[13] != '-' || value[18] != '-' || value[23] != '-' || value[14] != '7' {
		return false
	}
	if !strings.ContainsRune("89ab", rune(value[19])) {
		return false
	}
	for index, char := range value {
		if index == 8 || index == 13 || index == 18 || index == 23 {
			continue
		}
		if (char < '0' || char > '9') && (char < 'a' || char > 'f') {
			return false
		}
	}
	return true
}

func writeCommandString(out *bytes.Buffer, value string) {
	var encoded [binary.MaxVarintLen64]byte
	n := binary.PutUvarint(encoded[:], uint64(len(value)))
	out.Write(encoded[:n])
	out.WriteString(value)
}
