package layoutvalidationviewv1

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

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

// CanonicalizeView returns a defensive value with every top-level and nested
// array in the exact v1 bytewise order. Duplicate inputs are rejected rather
// than collapsed.
func CanonicalizeView(view View) (View, error) {
	if err := ValidateView(view); err != nil {
		return View{}, err
	}
	canonical := view
	canonical.Objects = append([]ObjectContract{}, view.Objects...)
	for index := range canonical.Objects {
		canonical.Objects[index].Capabilities = append([]CapabilityID{}, view.Objects[index].Capabilities...)
		sort.Slice(canonical.Objects[index].Capabilities, func(left, right int) bool {
			return canonical.Objects[index].Capabilities[left] < canonical.Objects[index].Capabilities[right]
		})
	}
	canonical.Fields = append([]FieldContract{}, view.Fields...)
	for index := range canonical.Fields {
		canonical.Fields[index].ReferenceTargets = append([]ObjectKey{}, view.Fields[index].ReferenceTargets...)
		sort.Slice(canonical.Fields[index].ReferenceTargets, func(left, right int) bool {
			return objectKeyLess(canonical.Fields[index].ReferenceTargets[left], canonical.Fields[index].ReferenceTargets[right])
		})
		if view.Fields[index].EditContractKey != nil {
			value := *view.Fields[index].EditContractKey
			canonical.Fields[index].EditContractKey = &value
		}
	}
	canonical.Relationships = append([]RelationshipContract{}, view.Relationships...)
	canonical.RelatedLists = append([]RelatedListContract{}, view.RelatedLists...)
	for index := range canonical.RelatedLists {
		canonical.RelatedLists[index].Capabilities = append([]CapabilityID{}, view.RelatedLists[index].Capabilities...)
		sort.Slice(canonical.RelatedLists[index].Capabilities, func(left, right int) bool {
			return canonical.RelatedLists[index].Capabilities[left] < canonical.RelatedLists[index].Capabilities[right]
		})
	}
	canonical.Actions = append([]ActionContract{}, view.Actions...)
	for index := range canonical.Actions {
		canonical.Actions[index].Capabilities = append([]CapabilityID{}, view.Actions[index].Capabilities...)
		sort.Slice(canonical.Actions[index].Capabilities, func(left, right int) bool {
			return canonical.Actions[index].Capabilities[left] < canonical.Actions[index].Capabilities[right]
		})
		if view.Actions[index].TargetObjectKey != nil {
			value := *view.Actions[index].TargetObjectKey
			canonical.Actions[index].TargetObjectKey = &value
		}
	}
	canonical.RendererComponents = append([]RendererComponentContract{}, view.RendererComponents...)
	for index := range canonical.RendererComponents {
		canonical.RendererComponents[index].Capabilities = append([]CapabilityID{}, view.RendererComponents[index].Capabilities...)
		sort.Slice(canonical.RendererComponents[index].Capabilities, func(left, right int) bool {
			return canonical.RendererComponents[index].Capabilities[left] < canonical.RendererComponents[index].Capabilities[right]
		})
	}
	canonical.AcceptedLayoutFormats = append([]LayoutFormatContract{}, view.AcceptedLayoutFormats...)
	canonical.BundleAuthorityEvidence = append([]BundleAuthorityEvidenceRef{}, view.BundleAuthorityEvidence...)

	sort.Slice(canonical.Objects, func(left, right int) bool {
		return objectKeyLess(canonical.Objects[left].Key, canonical.Objects[right].Key)
	})
	sort.Slice(canonical.Fields, func(left, right int) bool {
		return fieldKeyLess(canonical.Fields[left].Key, canonical.Fields[right].Key)
	})
	sort.Slice(canonical.Relationships, func(left, right int) bool {
		leftValue, rightValue := canonical.Relationships[left], canonical.Relationships[right]
		if leftValue.ReferenceFieldKey != rightValue.ReferenceFieldKey {
			return fieldKeyLess(leftValue.ReferenceFieldKey, rightValue.ReferenceFieldKey)
		}
		return objectKeyLess(leftValue.TargetObjectKey, rightValue.TargetObjectKey)
	})
	sort.Slice(canonical.RelatedLists, func(left, right int) bool {
		leftValue, rightValue := canonical.RelatedLists[left], canonical.RelatedLists[right]
		if leftValue.ParentObjectKey != rightValue.ParentObjectKey {
			return objectKeyLess(leftValue.ParentObjectKey, rightValue.ParentObjectKey)
		}
		return fieldKeyLess(leftValue.ReferenceFieldKey, rightValue.ReferenceFieldKey)
	})
	sort.Slice(canonical.Actions, func(left, right int) bool {
		leftValue, rightValue := canonical.Actions[left], canonical.Actions[right]
		if leftValue.ObjectKey != rightValue.ObjectKey {
			return objectKeyLess(leftValue.ObjectKey, rightValue.ObjectKey)
		}
		return namespacedKeyLess(leftValue.Key, rightValue.Key)
	})
	sort.Slice(canonical.RendererComponents, func(left, right int) bool {
		return namespacedKeyLess(canonical.RendererComponents[left].Key, canonical.RendererComponents[right].Key)
	})
	sort.Slice(canonical.AcceptedLayoutFormats, func(left, right int) bool {
		return canonical.AcceptedLayoutFormats[left].LayoutFormatVersion < canonical.AcceptedLayoutFormats[right].LayoutFormatVersion
	})
	sort.Slice(canonical.BundleAuthorityEvidence, func(left, right int) bool {
		return bundleKeyLess(canonical.BundleAuthorityEvidence[left].BundleKey, canonical.BundleAuthorityEvidence[right].BundleKey)
	})
	return canonical, nil
}

// CanonicalViewSize returns the exact complete v1 byte length without
// allocating the complete byte buffer.
func CanonicalViewSize(view View) (uint64, error) {
	if err := ValidateView(view); err != nil {
		return 0, err
	}
	return measureCanonicalView(view, MaxCanonicalViewBytes)
}

// CanonicalViewBytes returns the complete exact v1 canonical encoding.
func CanonicalViewBytes(view View) ([]byte, error) {
	canonical, err := CanonicalizeView(view)
	if err != nil {
		return nil, err
	}
	size, err := measureCanonicalView(canonical, MaxCanonicalViewBytes)
	if err != nil {
		return nil, err
	}
	var out bytes.Buffer
	out.Grow(int(size))
	writeString(&out, canonical.CanonicalizationVersion)
	writeString(&out, canonical.KeyGrammarVersion)
	writeString(&out, canonical.ObjectKeyNormalizationVersion)
	writeString(&out, canonical.FieldKeyNormalizationVersion)
	writeString(&out, canonical.SourceCatalogSnapshot.OrgID)
	writeUint(&out, canonical.SourceCatalogSnapshot.CatalogRevision)
	writeString(&out, canonical.SourceCatalogSnapshot.CatalogSnapshotDigest)
	writeUint(&out, canonical.SourceCatalogSnapshot.SchemaGeneration)
	writeUint(&out, canonical.SourceCatalogSnapshot.EngineGeneration)
	writeString(&out, canonical.CatalogAssignmentIdentityViewDigest)
	writeString(&out, canonical.RequiredBundleKeySetDigest)

	writeUint(&out, uint64(len(canonical.Objects)))
	for _, object := range canonical.Objects {
		writeObjectKey(&out, object.Key)
		writeStrings(&out, object.Capabilities)
	}
	writeUint(&out, uint64(len(canonical.Fields)))
	for _, field := range canonical.Fields {
		writeFieldKey(&out, field.Key)
		writeString(&out, string(field.DataTypeID))
		out.WriteByte(relationshipKindByte(field.RelationshipKind))
		writeBool(&out, field.OwnsPolymorphicDiscriminator)
		writeBool(&out, field.RequiredOnCreate)
		writeBool(&out, field.Nillable)
		writeBool(&out, field.DefaultedOnCreate)
		writeBool(&out, field.Createable)
		writeBool(&out, field.Updateable)
		if field.EditContractKey == nil {
			out.WriteByte(0x00)
		} else {
			out.WriteByte(0x01)
			writeNamespacedKey(&out, *field.EditContractKey)
		}
		writeNamespacedKey(&out, field.RenderContractKey)
		writeUint(&out, uint64(len(field.ReferenceTargets)))
		for _, target := range field.ReferenceTargets {
			writeObjectKey(&out, target)
		}
	}
	writeUint(&out, uint64(len(canonical.Relationships)))
	for _, relationship := range canonical.Relationships {
		writeFieldKey(&out, relationship.ReferenceFieldKey)
		writeObjectKey(&out, relationship.TargetObjectKey)
		out.WriteByte(relationshipKindByte(relationship.Kind))
	}
	writeUint(&out, uint64(len(canonical.RelatedLists)))
	for _, relatedList := range canonical.RelatedLists {
		writeObjectKey(&out, relatedList.ParentObjectKey)
		writeFieldKey(&out, relatedList.ReferenceFieldKey)
		writeStrings(&out, relatedList.Capabilities)
	}
	writeUint(&out, uint64(len(canonical.Actions)))
	for _, action := range canonical.Actions {
		writeObjectKey(&out, action.ObjectKey)
		writeNamespacedKey(&out, action.Key)
		writeString(&out, string(action.ActionKindID))
		if action.TargetObjectKey == nil {
			out.WriteByte(0x00)
		} else {
			out.WriteByte(0x01)
			writeObjectKey(&out, *action.TargetObjectKey)
		}
		writeUint(&out, action.ContractVersion)
		writeString(&out, action.ContractDigest)
		writeStrings(&out, action.Capabilities)
	}
	writeUint(&out, uint64(len(canonical.RendererComponents)))
	for _, renderer := range canonical.RendererComponents {
		writeNamespacedKey(&out, renderer.Key)
		writeUint(&out, renderer.ContractVersion)
		writeString(&out, renderer.ContractDigest)
		writeStrings(&out, renderer.Capabilities)
	}
	writeUint(&out, uint64(len(canonical.AcceptedLayoutFormats)))
	for _, format := range canonical.AcceptedLayoutFormats {
		writeString(&out, string(format.LayoutFormatVersion))
		writeString(&out, format.ContractDigest)
	}
	writeString(&out, layoutbundlekeyv1.BundleKeyVersion)
	writeUint(&out, uint64(len(canonical.BundleAuthorityEvidence)))
	for _, evidence := range canonical.BundleAuthorityEvidence {
		writeBundleKeyTuple(&out, evidence.BundleKey)
		writeString(&out, evidence.EvidenceVersion)
		writeString(&out, evidence.EvidenceDigest)
	}
	return out.Bytes(), nil
}

func measureCanonicalView(view View, limit uint64) (uint64, error) {
	measure := canonicalMeasure{limit: limit}
	measure.string(view.CanonicalizationVersion)
	measure.string(view.KeyGrammarVersion)
	measure.string(view.ObjectKeyNormalizationVersion)
	measure.string(view.FieldKeyNormalizationVersion)
	measure.string(view.SourceCatalogSnapshot.OrgID)
	measure.uint(view.SourceCatalogSnapshot.CatalogRevision)
	measure.string(view.SourceCatalogSnapshot.CatalogSnapshotDigest)
	measure.uint(view.SourceCatalogSnapshot.SchemaGeneration)
	measure.uint(view.SourceCatalogSnapshot.EngineGeneration)
	measure.string(view.CatalogAssignmentIdentityViewDigest)
	measure.string(view.RequiredBundleKeySetDigest)
	measure.uint(uint64(len(view.Objects)))
	for _, object := range view.Objects {
		measure.objectKey(object.Key)
		measure.capabilities(object.Capabilities)
		if measure.exceeded {
			return 0, canonicalSizeError(limit)
		}
	}
	measure.uint(uint64(len(view.Fields)))
	for _, field := range view.Fields {
		measure.fieldKey(field.Key)
		measure.string(string(field.DataTypeID))
		measure.add(7)
		measure.add(1)
		if field.EditContractKey != nil {
			measure.namespacedKey(*field.EditContractKey)
		}
		measure.namespacedKey(field.RenderContractKey)
		measure.uint(uint64(len(field.ReferenceTargets)))
		for _, target := range field.ReferenceTargets {
			measure.objectKey(target)
			if measure.exceeded {
				return 0, canonicalSizeError(limit)
			}
		}
		if measure.exceeded {
			return 0, canonicalSizeError(limit)
		}
	}
	measure.uint(uint64(len(view.Relationships)))
	for _, relationship := range view.Relationships {
		measure.fieldKey(relationship.ReferenceFieldKey)
		measure.objectKey(relationship.TargetObjectKey)
		measure.add(1)
		if measure.exceeded {
			return 0, canonicalSizeError(limit)
		}
	}
	measure.uint(uint64(len(view.RelatedLists)))
	for _, relatedList := range view.RelatedLists {
		measure.objectKey(relatedList.ParentObjectKey)
		measure.fieldKey(relatedList.ReferenceFieldKey)
		measure.capabilities(relatedList.Capabilities)
		if measure.exceeded {
			return 0, canonicalSizeError(limit)
		}
	}
	measure.uint(uint64(len(view.Actions)))
	for _, action := range view.Actions {
		measure.objectKey(action.ObjectKey)
		measure.namespacedKey(action.Key)
		measure.string(string(action.ActionKindID))
		measure.add(1)
		if action.TargetObjectKey != nil {
			measure.objectKey(*action.TargetObjectKey)
		}
		measure.uint(action.ContractVersion)
		measure.string(action.ContractDigest)
		measure.capabilities(action.Capabilities)
		if measure.exceeded {
			return 0, canonicalSizeError(limit)
		}
	}
	measure.uint(uint64(len(view.RendererComponents)))
	for _, renderer := range view.RendererComponents {
		measure.namespacedKey(renderer.Key)
		measure.uint(renderer.ContractVersion)
		measure.string(renderer.ContractDigest)
		measure.capabilities(renderer.Capabilities)
		if measure.exceeded {
			return 0, canonicalSizeError(limit)
		}
	}
	measure.uint(uint64(len(view.AcceptedLayoutFormats)))
	for _, format := range view.AcceptedLayoutFormats {
		measure.string(string(format.LayoutFormatVersion))
		measure.string(format.ContractDigest)
		if measure.exceeded {
			return 0, canonicalSizeError(limit)
		}
	}
	measure.string(layoutbundlekeyv1.BundleKeyVersion)
	measure.uint(uint64(len(view.BundleAuthorityEvidence)))
	for _, evidence := range view.BundleAuthorityEvidence {
		measure.add(1)
		measure.string(evidence.BundleKey.SlotOwnerNamespace)
		measure.string(string(evidence.BundleKey.LocalComponentKey))
		measure.string(evidence.EvidenceVersion)
		measure.string(evidence.EvidenceDigest)
		if measure.exceeded {
			return 0, canonicalSizeError(limit)
		}
	}
	if measure.exceeded {
		return 0, canonicalSizeError(limit)
	}
	return measure.size, nil
}

type canonicalMeasure struct {
	size     uint64
	limit    uint64
	exceeded bool
}

func (measure *canonicalMeasure) add(value uint64) {
	if measure.exceeded {
		return
	}
	if value > measure.limit-measure.size {
		measure.exceeded = true
		return
	}
	measure.size += value
}

func (measure *canonicalMeasure) uint(value uint64) {
	measure.add(canonicalUintSize(value))
}

func (measure *canonicalMeasure) string(value string) {
	measure.uint(uint64(len(value)))
	measure.add(uint64(len(value)))
}

func (measure *canonicalMeasure) capabilities(values []CapabilityID) {
	if measure.exceeded {
		return
	}
	measure.uint(uint64(len(values)))
	for _, value := range values {
		measure.string(string(value))
		if measure.exceeded {
			return
		}
	}
}

func canonicalSizeError(limit uint64) error {
	return fmt.Errorf("View canonical bytes exceed %d", limit)
}

func (measure *canonicalMeasure) objectKey(key ObjectKey) {
	measure.string(key.NormalizedAPIName)
}

func (measure *canonicalMeasure) fieldKey(key FieldKey) {
	measure.objectKey(key.ObjectKey)
	measure.string(key.NormalizedFieldAPIName)
}

func (measure *canonicalMeasure) namespacedKey(key NamespacedContractKey) {
	measure.string(key.NamespaceID)
	measure.string(key.LocalKey)
}

func objectKeyLess(left, right ObjectKey) bool {
	return left.NormalizedAPIName < right.NormalizedAPIName
}

func fieldKeyLess(left, right FieldKey) bool {
	if left.ObjectKey != right.ObjectKey {
		return objectKeyLess(left.ObjectKey, right.ObjectKey)
	}
	return left.NormalizedFieldAPIName < right.NormalizedFieldAPIName
}

func namespacedKeyLess(left, right NamespacedContractKey) bool {
	return left.NamespaceID < right.NamespaceID ||
		(left.NamespaceID == right.NamespaceID && left.LocalKey < right.LocalKey)
}

func bundleKeyLess(left, right layoutbundlekeyv1.BundleKey) bool {
	leftKind, _ := layoutbundlekeyv1.CanonicalSlotOwnerKindByte(left.SlotOwnerKind)
	rightKind, _ := layoutbundlekeyv1.CanonicalSlotOwnerKindByte(right.SlotOwnerKind)
	if leftKind != rightKind {
		return leftKind < rightKind
	}
	return left.SlotOwnerNamespace < right.SlotOwnerNamespace ||
		(left.SlotOwnerNamespace == right.SlotOwnerNamespace && string(left.LocalComponentKey) < string(right.LocalComponentKey))
}

func relationshipKindByte(kind RelationshipKind) byte {
	if kind == RelationshipKindLookup {
		return 0x01
	}
	if kind == RelationshipKindMasterDetail {
		return 0x02
	}
	return 0x00
}

func writeObjectKey(out *bytes.Buffer, key ObjectKey) {
	writeString(out, key.NormalizedAPIName)
}

func writeFieldKey(out *bytes.Buffer, key FieldKey) {
	writeObjectKey(out, key.ObjectKey)
	writeString(out, key.NormalizedFieldAPIName)
}

func writeNamespacedKey(out *bytes.Buffer, key NamespacedContractKey) {
	writeString(out, key.NamespaceID)
	writeString(out, key.LocalKey)
}

func writeBundleKeyTuple(out *bytes.Buffer, key layoutbundlekeyv1.BundleKey) {
	kind, _ := layoutbundlekeyv1.CanonicalSlotOwnerKindByte(key.SlotOwnerKind)
	out.WriteByte(kind)
	writeString(out, key.SlotOwnerNamespace)
	writeString(out, string(key.LocalComponentKey))
}

func writeStrings(out *bytes.Buffer, values []CapabilityID) {
	writeUint(out, uint64(len(values)))
	for _, value := range values {
		writeString(out, string(value))
	}
}

func writeBool(out *bytes.Buffer, value bool) {
	if value {
		out.WriteByte(0x01)
		return
	}
	out.WriteByte(0x00)
}

func canonicalUintSize(value uint64) uint64 {
	var encoded [binary.MaxVarintLen64]byte
	return uint64(binary.PutUvarint(encoded[:], value))
}

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

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