package layoutactivationsetv1

import (
	"fmt"
	"unicode/utf8"

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

// ValidateLayoutBundleRevisionID validates an exact opaque target-local ID.
func ValidateLayoutBundleRevisionID(value LayoutBundleRevisionID) error {
	return validateOpaqueID("LayoutBundleRevisionID", string(value))
}

// ValidateLayoutValidationID validates an exact opaque target-local ID.
func ValidateLayoutValidationID(value LayoutValidationID) error {
	return validateOpaqueID("LayoutValidationID", string(value))
}

// ValidateSet validates the complete value without loading referenced
// revisions, resolved views, validations, provenance, or current state.
func ValidateSet(set Set) error {
	if set.CanonicalizationVersion != CanonicalizationVersion {
		return fmt.Errorf("Set.CanonicalizationVersion must equal %q", CanonicalizationVersion)
	}
	if err := catalogpublicationv1.ValidateCatalogSchemaBinding(set.CatalogSchema); err != nil {
		return fmt.Errorf("Set.CatalogSchema: %w", err)
	}
	if err := layoutroutingsecurityv1.ValidateBinding(set.RoutingSecurity); err != nil {
		return fmt.Errorf("Set.RoutingSecurity: %w", err)
	}
	if set.OrgID != set.CatalogSchema.CatalogSnapshot.OrgID || set.OrgID != set.RoutingSecurity.OrgID {
		return fmt.Errorf("Set OrgIDs must match exactly across both authority bindings")
	}
	if err := appauthority.ValidateDigest(set.RequiredBundleKeySetDigest); err != nil {
		return fmt.Errorf("Set.RequiredBundleKeySetDigest: %w", err)
	}
	if err := appauthority.ValidateDigest(set.PreparationProvenanceDigest); err != nil {
		return fmt.Errorf("Set.PreparationProvenanceDigest: %w", err)
	}
	if set.Entries == nil {
		return fmt.Errorf("Set.Entries must be present")
	}
	if len(set.Entries) > MaxActivationEntries {
		return fmt.Errorf("Set.Entries exceeds %d entries", MaxActivationEntries)
	}
	if err := validateCanonicalSetSize(set, MaxCanonicalSetBytes); err != nil {
		return err
	}

	keys := make([]layoutbundlekeyv1.BundleKey, len(set.Entries))
	seenKeys := make(map[string]struct{}, len(set.Entries))
	seenRevisionIDs := make(map[LayoutBundleRevisionID]struct{}, len(set.Entries))
	seenValidationIDs := make(map[LayoutValidationID]struct{}, len(set.Entries))
	for index, entry := range set.Entries {
		if err := layoutbundlekeyv1.ValidateBundleKey(entry.BundleKey); err != nil {
			return fmt.Errorf("Set.Entries[%d].BundleKey: %w", index, err)
		}
		tuple, err := layoutbundlekeyv1.CanonicalBundleKeyTupleBytes(entry.BundleKey)
		if err != nil {
			return fmt.Errorf("Set.Entries[%d].BundleKey: %w", index, err)
		}
		if _, duplicate := seenKeys[string(tuple)]; duplicate {
			return fmt.Errorf("Set.Entries contains a duplicate BundleKey")
		}
		if err := ValidateLayoutBundleRevisionID(entry.LayoutBundleRevisionID); err != nil {
			return fmt.Errorf("Set.Entries[%d]: %w", index, err)
		}
		if _, duplicate := seenRevisionIDs[entry.LayoutBundleRevisionID]; duplicate {
			return fmt.Errorf("Set.Entries contains a duplicate LayoutBundleRevisionID")
		}
		if err := ValidateLayoutValidationID(entry.LayoutValidationID); err != nil {
			return fmt.Errorf("Set.Entries[%d]: %w", index, err)
		}
		if _, duplicate := seenValidationIDs[entry.LayoutValidationID]; duplicate {
			return fmt.Errorf("Set.Entries contains a duplicate LayoutValidationID")
		}
		if err := appauthority.ValidateDigest(entry.ResolvedLayoutViewDigest); err != nil {
			return fmt.Errorf("Set.Entries[%d].ResolvedLayoutViewDigest: %w", index, err)
		}

		keys[index] = entry.BundleKey
		seenKeys[string(tuple)] = struct{}{}
		seenRevisionIDs[entry.LayoutBundleRevisionID] = struct{}{}
		seenValidationIDs[entry.LayoutValidationID] = struct{}{}
	}

	requiredDigest, err := layoutbundlekeyv1.RequiredBundleKeySetDigest(layoutbundlekeyv1.RequiredBundleKeySet{
		Version:    layoutbundlekeyv1.RequiredBundleKeySetVersion,
		BundleKeys: keys,
	})
	if err != nil {
		return fmt.Errorf("Set required BundleKey set: %w", err)
	}
	if requiredDigest != set.RequiredBundleKeySetDigest {
		return fmt.Errorf("Set.RequiredBundleKeySetDigest does not match the complete entry key set")
	}
	return nil
}

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