package systemlayoutbundleauthorityv1

import (
	"bytes"
	"encoding/binary"
	"fmt"

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

// CanonicalSlotValueBytes returns exactly S(ValueVersion), the four-field
// versioned BundleKey grammar, and one state byte.
func CanonicalSlotValueBytes(value SystemLayoutBundleSlotV1) ([]byte, error) {
	if err := ValidateSlotValue(value); err != nil {
		return nil, err
	}
	bundleKeyBytes, err := canonicalBundleKeyBytes(value.BundleKey)
	if err != nil {
		return nil, err
	}
	stateByte, err := canonicalSlotStateByte(value.State)
	if err != nil {
		return nil, err
	}
	var out bytes.Buffer
	writeCanonicalString(&out, value.ValueVersion)
	out.Write(bundleKeyBytes)
	out.WriteByte(stateByte)
	return out.Bytes(), nil
}

// CanonicalCommandRequestBytes returns the complete v1 request-digest grammar.
// CommandID, authorization, audit, transport, timestamps, and current reads are
// intentionally absent.
func CanonicalCommandRequestBytes(request SystemLayoutBundleSlotCommandRequest) ([]byte, error) {
	if err := ValidateCommandRequest(request); err != nil {
		return nil, err
	}
	operationByte, err := canonicalOperationByte(request.Operation)
	if err != nil {
		return nil, err
	}
	targetBytes, err := canonicalBundleKeyBytes(request.TargetBundleKey)
	if err != nil {
		return nil, err
	}
	desiredBytes, err := CanonicalSlotValueBytes(request.DesiredSlotValue)
	if err != nil {
		return nil, err
	}
	priorByte, err := canonicalExpectedPriorByte(request.ExpectedPrior.Variant)
	if err != nil {
		return nil, err
	}

	var out bytes.Buffer
	writeCanonicalString(&out, CommandRequestDigestVersion)
	out.WriteByte(operationByte)
	out.Write(targetBytes)
	out.Write(desiredBytes)
	out.WriteByte(priorByte)
	if request.ExpectedPrior.Variant == ExpectedPriorExact {
		writeCanonicalString(&out, request.ExpectedPrior.SlotValueDigest)
	}
	return out.Bytes(), nil
}

// CanonicalEvidenceBytes returns the complete v1 system-authority evidence
// grammar using minimal unsigned LEB128 for strings and source integers.
func CanonicalEvidenceBytes(evidence SystemLayoutBundleAuthorityEvidenceV1) ([]byte, error) {
	if err := ValidateEvidence(evidence); err != nil {
		return nil, err
	}
	bundleKeyBytes, err := canonicalBundleKeyBytes(evidence.BundleKey)
	if err != nil {
		return nil, err
	}
	stateByte, err := canonicalSlotStateByte(evidence.SlotState)
	if err != nil {
		return nil, err
	}

	var out bytes.Buffer
	writeCanonicalString(&out, evidence.EvidenceVersion)
	writeCanonicalString(&out, evidence.SourceCatalogSnapshot.OrgID)
	writeCanonicalUint(&out, evidence.SourceCatalogSnapshot.CatalogRevision)
	writeCanonicalString(&out, evidence.SourceCatalogSnapshot.CatalogSnapshotDigest)
	writeCanonicalUint(&out, evidence.SourceCatalogSnapshot.SchemaGeneration)
	writeCanonicalUint(&out, evidence.SourceCatalogSnapshot.EngineGeneration)
	out.Write(bundleKeyBytes)
	writeCanonicalString(&out, evidence.SlotValueVersion)
	out.WriteByte(stateByte)
	return out.Bytes(), nil
}

func canonicalBundleKeyBytes(key BundleKey) ([]byte, error) {
	if err := ValidateBundleKey(key); err != nil {
		return nil, err
	}
	// ValidateBundleKey already proved that the explicit version field equals
	// the shared v1 constant, so the shared standalone grammar emits the exact
	// required S(BundleKeyVersion) prefix and tuple bytes.
	return layoutbundlekeyv1.CanonicalBundleKeyBytes(portableBundleKey(key))
}

func canonicalSlotStateByte(state SlotState) (byte, error) {
	switch state {
	case SlotStateActive:
		return 0x01, nil
	case SlotStateTombstone:
		return 0x02, nil
	default:
		return 0, fmt.Errorf("SlotState must be ACTIVE or TOMBSTONE")
	}
}

func canonicalOperationByte(operation Operation) (byte, error) {
	switch operation {
	case OperationCreate:
		return 0x01, nil
	case OperationRetire:
		return 0x02, nil
	default:
		return 0, fmt.Errorf("Operation must be CREATE or RETIRE")
	}
}

func canonicalExpectedPriorByte(variant ExpectedPriorVariant) (byte, error) {
	switch variant {
	case ExpectedPriorAbsent:
		return 0x00, nil
	case ExpectedPriorExact:
		return 0x01, nil
	default:
		return 0, fmt.Errorf("ExpectedPrior.Variant must be ABSENT or EXACT")
	}
}

func writeCanonicalString(out *bytes.Buffer, value string) {
	writeCanonicalUint(out, uint64(len(value)))
	out.WriteString(value)
}

func writeCanonicalUint(out *bytes.Buffer, value uint64) {
	var encoded [binary.MaxVarintLen64]byte
	count := binary.PutUvarint(encoded[:], value)
	out.Write(encoded[:count])
}
