package connectorbindingownerv1

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

	catalogv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
)

const (
	// DefinitionSetProtocolV1 identifies the closed parent, ordering, and
	// ordered-set digest grammar. A stored authority must carry it explicitly;
	// readers never infer this protocol for a legacy generation.
	DefinitionSetProtocolV1 = "nexuscrm.connector-binding-owner-definition-set/v1"

	// MaxDefinitionCount shares Catalog's closed IntegrationBinding view
	// bound. A single owner generation cannot publish more definitions than a
	// complete authorized binding view can address.
	MaxDefinitionCount = catalogv1.MaxBindingCount

	// MaxDefinitionSetBytes bounds the exact sum of every canonical
	// DefinitionSetEntry.EnvelopeJSON length in one complete set. Header and
	// projected storage fields are not part of this byte budget. The aggregate
	// bound prevents the individually bounded envelopes from amplifying into an
	// unbounded publisher or source-free reader allocation.
	MaxDefinitionSetBytes uint64 = 64 << 20

	definitionOrderedSetDigestDomain = "nexuscrm.connector-binding-owner-definition-ordered-set/v1"
)

// DefinitionSetCoordinate is the exact portable parent generation shared by
// every definition entry. Target-local storage row and foreign-key identities
// deliberately remain outside this neutral contract.
type DefinitionSetCoordinate struct {
	OwnerPack          string
	PackInstallationID string
	PackPayloadDigest  string
}

// DefinitionSetHeader is the complete parent commitment. DefinitionCount and
// OrderedSetDigest make an explicit empty authority distinct from a missing
// authority and let a source-free reader detect incomplete stored rows.
type DefinitionSetHeader struct {
	Protocol string
	DefinitionSetCoordinate
	DefinitionCount  uint64
	OrderedSetDigest string
}

// DefinitionSetEntry is one persistence-shaped child value. The projected
// identity and EnvelopeDigest are deliberate redundancy: validation requires
// byte-exact agreement with EnvelopeJSON before any projected field is trusted.
type DefinitionSetEntry struct {
	Ordinal            uint64
	Kind               catalogv1.IntegrationBindingKind
	OwnerPack          string
	PackInstallationID string
	PackPayloadDigest  string
	BusinessKey        string
	DefinitionVersion  uint64
	EnvelopeJSON       []byte
	EnvelopeDigest     string
}

// DefinitionSet is one immutable complete owner publication. Entries are in
// ascending byte order by (Kind, BusinessKey) with contiguous zero-based
// ordinals. A valid set may contain zero entries.
type DefinitionSet struct {
	Header  DefinitionSetHeader
	Entries []DefinitionSetEntry
}

// NewDefinitionSet validates and canonicalizes the complete owner envelopes
// for one immutable generation. Input order and caller-owned byte slices are
// not retained. An empty input creates the canonical explicit empty set.
func NewDefinitionSet(
	coordinate DefinitionSetCoordinate,
	definitions []DefinitionEnvelope,
) (DefinitionSet, error) {
	if err := validateDefinitionSetCoordinate(coordinate); err != nil {
		return DefinitionSet{}, err
	}
	if len(definitions) > MaxDefinitionCount {
		return DefinitionSet{}, fmt.Errorf(
			"DefinitionSet definitions exceed %d entries",
			MaxDefinitionCount,
		)
	}
	if err := preflightDefinitionSetAuthoringBytes(definitions); err != nil {
		return DefinitionSet{}, err
	}

	canonical := make([]DefinitionEnvelope, len(definitions))
	for index, definition := range definitions {
		if err := ValidateDefinitionEnvelope(definition); err != nil {
			return DefinitionSet{}, fmt.Errorf("DefinitionSet definitions[%d]: %w", index, err)
		}
		if err := validateDefinitionSetEntryVersion(definition.Identity); err != nil {
			return DefinitionSet{}, fmt.Errorf("DefinitionSet definitions[%d]: %w", index, err)
		}
		if err := validateDefinitionSetParentAgreement(coordinate, definition.Identity); err != nil {
			return DefinitionSet{}, fmt.Errorf("DefinitionSet definitions[%d]: %w", index, err)
		}
		canonical[index] = CloneDefinitionEnvelope(definition)
	}
	sort.Slice(canonical, func(left, right int) bool {
		return definitionSetNaturalKey(canonical[left].Identity) <
			definitionSetNaturalKey(canonical[right].Identity)
	})
	if err := validateCanonicalDefinitionSetOrder(canonical); err != nil {
		return DefinitionSet{}, err
	}

	entries := make([]DefinitionSetEntry, len(canonical))
	for index, definition := range canonical {
		raw, err := MarshalDefinitionEnvelopeJSON(definition)
		if err != nil {
			return DefinitionSet{}, fmt.Errorf("marshal DefinitionSet definitions[%d]: %w", index, err)
		}
		envelopeDigest, err := DefinitionEnvelopeDigest(definition)
		if err != nil {
			return DefinitionSet{}, fmt.Errorf("digest DefinitionSet definitions[%d]: %w", index, err)
		}
		entries[index] = DefinitionSetEntry{
			Ordinal:            uint64(index),
			Kind:               definition.Kind,
			OwnerPack:          definition.OwnerPack,
			PackInstallationID: definition.PackInstallationID,
			PackPayloadDigest:  definition.PackPayloadDigest,
			BusinessKey:        definition.BusinessKey,
			DefinitionVersion:  definition.DefinitionVersion,
			EnvelopeJSON:       raw,
			EnvelopeDigest:     envelopeDigest,
		}
	}
	header := DefinitionSetHeader{
		Protocol:                DefinitionSetProtocolV1,
		DefinitionSetCoordinate: coordinate,
		DefinitionCount:         uint64(len(entries)),
	}
	orderedSetDigest, err := definitionSetOrderedDigest(header, canonical)
	if err != nil {
		return DefinitionSet{}, err
	}
	header.OrderedSetDigest = orderedSetDigest
	return DefinitionSet{Header: header, Entries: entries}, nil
}

// ValidateDefinitionSet validates a complete stored publication without
// source files or mutable current/latest state. The returned envelopes are
// detached and remain in the sole canonical order.
func ValidateDefinitionSet(set DefinitionSet) ([]DefinitionEnvelope, error) {
	header := set.Header
	if header.Protocol != DefinitionSetProtocolV1 {
		return nil, fmt.Errorf(
			"DefinitionSet.Header.Protocol must equal %q",
			DefinitionSetProtocolV1,
		)
	}
	if err := validateDefinitionSetCoordinate(header.DefinitionSetCoordinate); err != nil {
		return nil, err
	}
	if header.DefinitionCount > MaxDefinitionCount || len(set.Entries) > MaxDefinitionCount {
		return nil, fmt.Errorf(
			"DefinitionSet entries exceed %d entries",
			MaxDefinitionCount,
		)
	}
	if header.DefinitionCount != uint64(len(set.Entries)) {
		return nil, fmt.Errorf(
			"DefinitionSet.Header.DefinitionCount is %d; stored entry count is %d",
			header.DefinitionCount,
			len(set.Entries),
		)
	}
	if err := validateDefinitionSetEntryBytes(set.Entries); err != nil {
		return nil, err
	}

	definitions := make([]DefinitionEnvelope, len(set.Entries))
	for index, entry := range set.Entries {
		if entry.Ordinal != uint64(index) {
			return nil, fmt.Errorf(
				"DefinitionSet.Entries[%d].Ordinal is %d",
				index,
				entry.Ordinal,
			)
		}
		definition, err := ParseDefinitionEnvelopeJSON(entry.EnvelopeJSON)
		if err != nil {
			return nil, fmt.Errorf("DefinitionSet.Entries[%d].EnvelopeJSON: %w", index, err)
		}
		if err := validateDefinitionSetEntryVersion(definition.Identity); err != nil {
			return nil, fmt.Errorf("DefinitionSet.Entries[%d]: %w", index, err)
		}
		if err := validateDefinitionSetParentAgreement(header.DefinitionSetCoordinate, definition.Identity); err != nil {
			return nil, fmt.Errorf("DefinitionSet.Entries[%d]: %w", index, err)
		}
		if entry.Kind != definition.Kind ||
			entry.OwnerPack != definition.OwnerPack ||
			entry.PackInstallationID != definition.PackInstallationID ||
			entry.PackPayloadDigest != definition.PackPayloadDigest ||
			entry.BusinessKey != definition.BusinessKey ||
			entry.DefinitionVersion != definition.DefinitionVersion {
			return nil, fmt.Errorf(
				"DefinitionSet.Entries[%d] projected identity disagrees with its canonical envelope",
				index,
			)
		}
		envelopeDigest, err := DefinitionEnvelopeDigest(definition)
		if err != nil {
			return nil, fmt.Errorf("DefinitionSet.Entries[%d] envelope digest: %w", index, err)
		}
		if entry.EnvelopeDigest != envelopeDigest {
			return nil, fmt.Errorf(
				"DefinitionSet.Entries[%d].EnvelopeDigest disagrees with its canonical envelope",
				index,
			)
		}
		definitions[index] = definition
	}
	if err := validateCanonicalDefinitionSetOrder(definitions); err != nil {
		return nil, err
	}
	orderedSetDigest, err := definitionSetOrderedDigest(header, definitions)
	if err != nil {
		return nil, err
	}
	if header.OrderedSetDigest != orderedSetDigest {
		return nil, fmt.Errorf(
			"DefinitionSet.Header.OrderedSetDigest disagrees with the complete stored entry set",
		)
	}
	return cloneDefinitionEnvelopes(definitions), nil
}

func preflightDefinitionSetAuthoringBytes(definitions []DefinitionEnvelope) error {
	var total uint64
	for index, definition := range definitions {
		raw, err := MarshalDefinitionEnvelopeJSON(definition)
		if err != nil {
			return fmt.Errorf("DefinitionSet definitions[%d]: %w", index, err)
		}
		total, err = addDefinitionSetBytes(total, uint64(len(raw)))
		if err != nil {
			return err
		}
	}
	return nil
}

func validateDefinitionSetEntryBytes(entries []DefinitionSetEntry) error {
	var total uint64
	for _, entry := range entries {
		var err error
		total, err = addDefinitionSetBytes(total, uint64(len(entry.EnvelopeJSON)))
		if err != nil {
			return err
		}
	}
	return nil
}

func addDefinitionSetBytes(total, next uint64) (uint64, error) {
	if total > MaxDefinitionSetBytes || next > MaxDefinitionSetBytes-total {
		return 0, fmt.Errorf(
			"DefinitionSet serialized envelopes exceed %d bytes",
			MaxDefinitionSetBytes,
		)
	}
	return total + next, nil
}

func validateDefinitionSetCoordinate(coordinate DefinitionSetCoordinate) error {
	if !validOpaqueIdentifier(coordinate.OwnerPack) {
		return fmt.Errorf("DefinitionSet coordinate OwnerPack is not canonical")
	}
	if !validOpaqueIdentifier(coordinate.PackInstallationID) {
		return fmt.Errorf("DefinitionSet coordinate PackInstallationID is not canonical")
	}
	if !packPayloadDigestPattern.MatchString(coordinate.PackPayloadDigest) {
		return fmt.Errorf("DefinitionSet coordinate PackPayloadDigest is invalid")
	}
	return nil
}

func validateDefinitionSetParentAgreement(
	coordinate DefinitionSetCoordinate,
	identity Identity,
) error {
	if identity.OwnerPack != coordinate.OwnerPack {
		return fmt.Errorf("OwnerPack disagrees with the parent generation")
	}
	if identity.PackInstallationID != coordinate.PackInstallationID {
		return fmt.Errorf("PackInstallationID disagrees with the parent generation")
	}
	if identity.PackPayloadDigest != coordinate.PackPayloadDigest {
		return fmt.Errorf("PackPayloadDigest disagrees with the parent generation")
	}
	return nil
}

func validateDefinitionSetEntryVersion(identity Identity) error {
	if identity.Kind != catalogv1.BindingConnectorInboundRoute && identity.DefinitionVersion != 1 {
		return fmt.Errorf(
			"definition %s/%s must use DefinitionVersion 1",
			identity.Kind,
			identity.BusinessKey,
		)
	}
	return nil
}

func validateCanonicalDefinitionSetOrder(definitions []DefinitionEnvelope) error {
	for index := 1; index < len(definitions); index++ {
		previous := definitionSetNaturalKey(definitions[index-1].Identity)
		current := definitionSetNaturalKey(definitions[index].Identity)
		if previous == current {
			return fmt.Errorf(
				"DefinitionSet contains duplicate identity %s/%s",
				definitions[index].Kind,
				definitions[index].BusinessKey,
			)
		}
		if previous > current {
			return fmt.Errorf("DefinitionSet entries are not in canonical Kind/BusinessKey order")
		}
	}
	return nil
}

func definitionSetNaturalKey(identity Identity) string {
	return string(identity.Kind) + "\x00" + identity.BusinessKey
}

func definitionSetOrderedDigest(
	header DefinitionSetHeader,
	definitions []DefinitionEnvelope,
) (string, error) {
	canonical := canonicalPrefix(definitionOrderedSetDigestDomain)
	canonical = appendCanonicalString(canonical, header.Protocol)
	canonical = appendCanonicalString(canonical, header.OwnerPack)
	canonical = appendCanonicalString(canonical, header.PackInstallationID)
	canonical = appendCanonicalString(canonical, header.PackPayloadDigest)
	canonical = binary.BigEndian.AppendUint64(canonical, header.DefinitionCount)
	for index, definition := range definitions {
		envelope, err := CanonicalDefinitionEnvelopeBytes(definition)
		if err != nil {
			return "", fmt.Errorf("canonicalize DefinitionSet definitions[%d]: %w", index, err)
		}
		canonical = binary.BigEndian.AppendUint64(canonical, uint64(index))
		canonical = appendCanonicalBytes(canonical, envelope)
	}
	return digestPrefixed(canonical), nil
}

func cloneDefinitionEnvelopes(definitions []DefinitionEnvelope) []DefinitionEnvelope {
	result := make([]DefinitionEnvelope, len(definitions))
	for index, definition := range definitions {
		result[index] = CloneDefinitionEnvelope(definition)
	}
	return result
}
