package tenantlayoutbundleauthorityv1

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

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

func CanonicalNamespaceValueBytes(value TenantNamespaceReservationV1) ([]byte, error) {
	if err := ValidateNamespaceValue(value); err != nil {
		return nil, err
	}
	stateByte, err := canonicalNamespaceStateByte(value.State)
	if err != nil {
		return nil, err
	}
	var out bytes.Buffer
	writeCanonicalString(&out, value.ValueVersion)
	writeCanonicalString(&out, value.TenantNamespaceID)
	out.WriteByte(stateByte)
	return out.Bytes(), nil
}

func CanonicalOrgBindingValueBytes(value TenantOrgNamespaceBindingV1) ([]byte, error) {
	if err := ValidateOrgBindingValue(value); err != nil {
		return nil, err
	}
	stateByte, err := canonicalOrgBindingStateByte(value.State)
	if err != nil {
		return nil, err
	}
	var out bytes.Buffer
	writeCanonicalString(&out, value.ValueVersion)
	writeCanonicalString(&out, value.OrgID)
	writeCanonicalString(&out, value.TenantNamespaceID)
	out.WriteByte(stateByte)
	return out.Bytes(), nil
}

func CanonicalSlotValueBytes(value TenantLayoutBundleSlotV1) ([]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
}

func CanonicalNamespaceCommandRequestBytes(request TenantNamespaceCommandRequest) ([]byte, error) {
	if err := ValidateNamespaceCommandRequest(request); err != nil {
		return nil, err
	}
	operationByte, err := canonicalNamespaceOperationByte(request.Operation)
	if err != nil {
		return nil, err
	}
	desiredBytes, err := CanonicalNamespaceValueBytes(request.DesiredNamespaceValue)
	if err != nil {
		return nil, err
	}
	priorByte, err := canonicalNamespaceExpectedPriorByte(request.ExpectedPriorNamespace.Variant)
	if err != nil {
		return nil, err
	}

	var out bytes.Buffer
	writeCanonicalString(&out, NamespaceCommandRequestDigestVersion)
	out.WriteByte(operationByte)
	writeCanonicalString(&out, request.TargetTenantNamespaceID)
	out.Write(desiredBytes)
	out.WriteByte(priorByte)
	if request.ExpectedPriorNamespace.Variant == NamespaceExpectedPriorExact {
		writeCanonicalString(&out, request.ExpectedPriorNamespace.NamespaceValueDigest)
	}
	return out.Bytes(), nil
}

func CanonicalOrgBindingCommandRequestBytes(request TenantOrgNamespaceBindingCommandRequest) ([]byte, error) {
	if err := ValidateOrgBindingCommandRequest(request); err != nil {
		return nil, err
	}
	operationByte, err := canonicalBindingOperationByte(request.Operation)
	if err != nil {
		return nil, err
	}
	desiredBytes, err := CanonicalOrgBindingValueBytes(request.DesiredBindingValue)
	if err != nil {
		return nil, err
	}
	priorByte, err := canonicalBindingExpectedPriorByte(request.ExpectedPriorBinding.Variant)
	if err != nil {
		return nil, err
	}

	var out bytes.Buffer
	writeCanonicalString(&out, BindingCommandRequestDigestVersion)
	out.WriteByte(operationByte)
	writeCanonicalString(&out, request.TargetOrgID)
	out.Write(desiredBytes)
	writeCanonicalString(&out, request.ExpectedNamespaceReservationDigest)
	out.WriteByte(priorByte)
	if request.ExpectedPriorBinding.Variant == BindingExpectedPriorExact {
		writeCanonicalString(&out, request.ExpectedPriorBinding.BindingValueDigest)
	}
	return out.Bytes(), nil
}

func CanonicalSlotCommandRequestBytes(request TenantLayoutBundleSlotCommandRequest) ([]byte, error) {
	if err := ValidateSlotCommandRequest(request); err != nil {
		return nil, err
	}
	operationByte, err := canonicalSlotOperationByte(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 := canonicalSlotExpectedPriorByte(request.ExpectedPriorSlot.Variant)
	if err != nil {
		return nil, err
	}

	var out bytes.Buffer
	writeCanonicalString(&out, SlotCommandRequestDigestVersion)
	out.WriteByte(operationByte)
	writeCanonicalString(&out, request.AuthorizingOrgID)
	out.Write(targetBytes)
	out.Write(desiredBytes)
	writeCanonicalString(&out, request.ExpectedNamespaceReservationDigest)
	writeCanonicalString(&out, request.ExpectedAuthorizingOrgBindingDigest)
	out.WriteByte(priorByte)
	if request.ExpectedPriorSlot.Variant == SlotExpectedPriorExact {
		writeCanonicalString(&out, request.ExpectedPriorSlot.SlotValueDigest)
	}
	return out.Bytes(), nil
}

func CanonicalEvidenceBytes(evidence TenantLayoutBundleAuthorityEvidenceV1) ([]byte, error) {
	if err := ValidateEvidence(evidence); err != nil {
		return nil, err
	}
	bundleKeyBytes, err := canonicalBundleKeyBytes(evidence.BundleKey)
	if err != nil {
		return nil, err
	}
	namespaceStateByte, err := canonicalNamespaceStateByte(evidence.NamespaceReservationState)
	if err != nil {
		return nil, err
	}
	bindingStateByte, err := canonicalOrgBindingStateByte(evidence.OrgBindingState)
	if err != nil {
		return nil, err
	}
	slotStateByte, 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.NamespaceReservationValueVersion)
	out.WriteByte(namespaceStateByte)
	writeCanonicalString(&out, evidence.OrgBindingValueVersion)
	out.WriteByte(bindingStateByte)
	writeCanonicalString(&out, evidence.SlotValueVersion)
	out.WriteByte(slotStateByte)
	return out.Bytes(), nil
}

func canonicalBundleKeyBytes(key BundleKey) ([]byte, error) {
	if err := ValidateBundleKey(key); err != nil {
		return nil, err
	}
	return layoutbundlekeyv1.CanonicalBundleKeyBytes(portableBundleKey(key))
}

func canonicalNamespaceStateByte(state NamespaceState) (byte, error) {
	switch state {
	case NamespaceStateActive:
		return 0x01, nil
	case NamespaceStateTombstone:
		return 0x02, nil
	default:
		return 0, fmt.Errorf("NamespaceState must be ACTIVE or TOMBSTONE")
	}
}

func canonicalOrgBindingStateByte(state OrgBindingState) (byte, error) {
	switch state {
	case OrgBindingStateActive:
		return 0x01, nil
	case OrgBindingStateTombstone:
		return 0x02, nil
	default:
		return 0, fmt.Errorf("OrgBindingState must be ACTIVE or TOMBSTONE")
	}
}

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 canonicalNamespaceOperationByte(operation NamespaceOperation) (byte, error) {
	switch operation {
	case NamespaceOperationReserve:
		return 0x01, nil
	case NamespaceOperationTombstone:
		return 0x02, nil
	default:
		return 0, fmt.Errorf("NamespaceOperation must be RESERVE or TOMBSTONE")
	}
}

func canonicalBindingOperationByte(operation BindingOperation) (byte, error) {
	switch operation {
	case BindingOperationBind:
		return 0x01, nil
	case BindingOperationTombstone:
		return 0x02, nil
	default:
		return 0, fmt.Errorf("BindingOperation must be BIND or TOMBSTONE")
	}
}

func canonicalSlotOperationByte(operation SlotOperation) (byte, error) {
	switch operation {
	case SlotOperationCreate:
		return 0x01, nil
	case SlotOperationRetire:
		return 0x02, nil
	default:
		return 0, fmt.Errorf("SlotOperation must be CREATE or RETIRE")
	}
}

func canonicalNamespaceExpectedPriorByte(variant NamespaceExpectedPriorVariant) (byte, error) {
	switch variant {
	case NamespaceExpectedPriorAbsent:
		return 0x00, nil
	case NamespaceExpectedPriorExact:
		return 0x01, nil
	default:
		return 0, fmt.Errorf("Namespace expected-prior variant must be ABSENT or EXACT")
	}
}

func canonicalBindingExpectedPriorByte(variant BindingExpectedPriorVariant) (byte, error) {
	switch variant {
	case BindingExpectedPriorAbsent:
		return 0x00, nil
	case BindingExpectedPriorExact:
		return 0x01, nil
	default:
		return 0, fmt.Errorf("Binding expected-prior variant must be ABSENT or EXACT")
	}
}

func canonicalSlotExpectedPriorByte(variant SlotExpectedPriorVariant) (byte, error) {
	switch variant {
	case SlotExpectedPriorAbsent:
		return 0x00, nil
	case SlotExpectedPriorExact:
		return 0x01, nil
	default:
		return 0, fmt.Errorf("Slot expected-prior 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])
}
