package appauthority

import (
	"encoding/hex"
	"fmt"
	"strconv"
	"strings"
	"unicode/utf8"
)

func ValidatePublisherID(value string) error { return validateNamedID("PublisherID", value, 2, false) }
func ValidateAppID(value string) error       { return validateNamedID("AppID", value, 3, false) }

// ValidateNamespaceID validates the shared neutral namespace-ID grammar.
func ValidateNamespaceID(value string) error { return validateNamedID("namespace ID", value, 2, false) }
func ValidateCapabilityID(value string) error {
	return validateNamedID("capability ID", value, 2, true)
}
func ValidateHandlerRef(value string) error { return validateNamedID("HandlerRef", value, 2, true) }
func ValidateDigest(value string) error     { return validateDigest(value) }

func ValidateAppInstallationID(value string) error {
	if !isLowercaseUUIDVersion7(value) {
		return fmt.Errorf("AppInstallationID must be a lowercase UUIDv7")
	}
	return nil
}

func ValidateHandlerBindingID(value string) error {
	if !isLowercaseUUIDVersion7(value) {
		return fmt.Errorf("HandlerBindingID must be a lowercase UUIDv7")
	}
	return nil
}

func (m AppManifest) Validate() error {
	if m.SchemaVersion != AppManifestSchemaVersion {
		return fmt.Errorf("app manifest: unsupported schema_version %q", m.SchemaVersion)
	}
	if err := ValidateAppID(m.AppID); err != nil {
		return err
	}
	if err := ValidatePublisherID(m.PublisherID); err != nil {
		return err
	}
	if _, err := parseSemVer(m.SemanticVersion); err != nil {
		return fmt.Errorf("app manifest semantic_version: %w", err)
	}
	if err := validateDigest(m.MetadataBundleDigest); err != nil {
		return fmt.Errorf("app manifest metadata_bundle_digest: %w", err)
	}
	if m.Dependencies == nil || m.OwnedNamespaces == nil || m.RequiredProtocols == nil || m.RequiredEngineCapabilities == nil || m.ProvidedCapabilities == nil {
		return fmt.Errorf("app manifest set arrays must be present, not null")
	}
	if err := validateDependencies(m.AppID, m.Dependencies); err != nil {
		return err
	}
	if err := validateSortedStrings("owned_namespaces", m.OwnedNamespaces, ValidateNamespaceID); err != nil {
		return err
	}
	if err := validateProtocolRequirements("required_protocols", m.RequiredProtocols); err != nil {
		return err
	}
	if err := validateSortedStrings("required_engine_capabilities", m.RequiredEngineCapabilities, ValidateCapabilityID); err != nil {
		return err
	}
	if err := validateProvidedCapabilities(m.ProvidedCapabilities); err != nil {
		return err
	}
	return m.FrontendCompatibility.validate()
}

func (b MetadataBundle) Validate(manifest AppManifest) error {
	if err := manifest.Validate(); err != nil {
		return err
	}
	if b.SchemaVersion != MetadataBundleSchemaVersion {
		return fmt.Errorf("metadata bundle: unsupported schema_version %q", b.SchemaVersion)
	}
	if b.Components == nil {
		return fmt.Errorf("metadata bundle components must be present, not null")
	}
	ownedNamespaces := stringSet(manifest.OwnedNamespaces)
	dependencyAliases := map[string]struct{}{}
	for _, dependency := range manifest.Dependencies {
		dependencyAliases[dependency.Alias] = struct{}{}
	}
	componentIDs := map[string]struct{}{}
	lastID := ""
	for i, component := range b.Components {
		if err := component.validate(ownedNamespaces, dependencyAliases); err != nil {
			return fmt.Errorf("metadata bundle component[%d]: %w", i, err)
		}
		if i > 0 && component.ComponentID <= lastID {
			return fmt.Errorf("metadata bundle components must be strictly sorted by component_id")
		}
		lastID = component.ComponentID
		componentIDs[component.ComponentID] = struct{}{}
	}
	for i, component := range b.Components {
		for _, dependency := range component.LocalDependencies {
			if _, exists := componentIDs[dependency]; !exists {
				return fmt.Errorf("metadata bundle component[%d]: unknown local dependency %q", i, dependency)
			}
			if dependency == component.ComponentID {
				return fmt.Errorf("metadata bundle component[%d]: self dependency is forbidden", i)
			}
		}
	}
	if err := rejectComponentCycles(b.Components); err != nil {
		return err
	}
	digest, err := MetadataBundleDigest(b, manifest)
	if err != nil {
		return err
	}
	if digest != manifest.MetadataBundleDigest {
		return fmt.Errorf("metadata bundle digest %s does not match manifest %s", digest, manifest.MetadataBundleDigest)
	}
	return nil
}

func validateDependencies(containingAppID string, values []Dependency) error {
	last := ""
	for i, value := range values {
		if err := validateSegment("dependency alias", value.Alias); err != nil {
			return fmt.Errorf("dependencies[%d]: %w", i, err)
		}
		if i > 0 && value.Alias <= last {
			return fmt.Errorf("dependencies must be strictly sorted by alias")
		}
		last = value.Alias
		if err := ValidateAppID(value.AppID); err != nil {
			return fmt.Errorf("dependencies[%d]: %w", i, err)
		}
		if value.AppID == containingAppID {
			return fmt.Errorf("dependencies[%d]: an App cannot depend on itself", i)
		}
		minimum, err := parseSemVer(value.MinimumVersionInclusive)
		if err != nil {
			return fmt.Errorf("dependencies[%d] minimum_version_inclusive: %w", i, err)
		}
		maximum, err := parseSemVer(value.MaximumVersionExclusive)
		if err != nil {
			return fmt.Errorf("dependencies[%d] maximum_version_exclusive: %w", i, err)
		}
		if compareSemVer(minimum, maximum) >= 0 {
			return fmt.Errorf("dependencies[%d]: minimum version must be lower than maximum", i)
		}
		if value.RequiredCapabilities == nil {
			return fmt.Errorf("dependencies[%d] required_capabilities must be present, not null", i)
		}
		if err := validateSortedStrings("dependency required_capabilities", value.RequiredCapabilities, ValidateCapabilityID); err != nil {
			return fmt.Errorf("dependencies[%d]: %w", i, err)
		}
	}
	return nil
}

func validateProtocolRequirements(label string, values []ProtocolRequirement) error {
	last := ""
	for i, value := range values {
		if err := value.validate(); err != nil {
			return fmt.Errorf("%s[%d]: %w", label, i, err)
		}
		if i > 0 && value.ProtocolID <= last {
			return fmt.Errorf("%s must be strictly sorted by protocol_id", label)
		}
		last = value.ProtocolID
	}
	return nil
}

func (p ProtocolRequirement) validate() error {
	if err := validateNamedID("protocol ID", p.ProtocolID, 2, false); err != nil {
		return err
	}
	if p.Major == 0 {
		return fmt.Errorf("protocol major must be positive")
	}
	return nil
}

func validateProvidedCapabilities(values []ProvidedCapability) error {
	last := ""
	for i, value := range values {
		if err := ValidateCapabilityID(value.CapabilityID); err != nil {
			return fmt.Errorf("provided_capabilities[%d]: %w", i, err)
		}
		if i > 0 && value.CapabilityID <= last {
			return fmt.Errorf("provided_capabilities must be strictly sorted by capability_id")
		}
		last = value.CapabilityID
		if value.RequiredRuntimeCapabilities == nil {
			return fmt.Errorf("provided_capabilities[%d] required_runtime_capabilities must be present, not null", i)
		}
		switch value.Kind {
		case CapabilityKindMetadata:
			if value.HandlerRef != "" || len(value.RequiredRuntimeCapabilities) != 0 {
				return fmt.Errorf("provided_capabilities[%d]: metadata capability cannot declare handler_ref or Runtime capabilities", i)
			}
		case CapabilityKindServiceHandler:
			if err := ValidateHandlerRef(value.HandlerRef); err != nil {
				return fmt.Errorf("provided_capabilities[%d]: %w", i, err)
			}
			if len(value.RequiredRuntimeCapabilities) == 0 {
				return fmt.Errorf("provided_capabilities[%d]: service-handler requires Runtime capabilities", i)
			}
		default:
			return fmt.Errorf("provided_capabilities[%d]: unknown kind %q", i, value.Kind)
		}
		if err := validateSortedStrings("provided required_runtime_capabilities", value.RequiredRuntimeCapabilities, ValidateCapabilityID); err != nil {
			return fmt.Errorf("provided_capabilities[%d]: %w", i, err)
		}
	}
	return nil
}

func (f FrontendCompatibility) validate() error {
	switch f.Mode {
	case FrontendModeNone:
		if f.HostSDK != nil || f.ContributionSchema != nil {
			return fmt.Errorf("frontend_compatibility mode none cannot declare protocols")
		}
	case FrontendModeRequired:
		if f.HostSDK == nil || f.ContributionSchema == nil {
			return fmt.Errorf("frontend_compatibility mode required needs host_sdk and contribution_schema")
		}
		if err := f.HostSDK.validate(); err != nil {
			return fmt.Errorf("frontend_compatibility host_sdk: %w", err)
		}
		if err := f.ContributionSchema.validate(); err != nil {
			return fmt.Errorf("frontend_compatibility contribution_schema: %w", err)
		}
	default:
		return fmt.Errorf("frontend_compatibility has unknown mode %q", f.Mode)
	}
	return nil
}

func (c Component) validate(ownedNamespaces, dependencyAliases map[string]struct{}) error {
	if err := validateComponentIdentity(c); err != nil {
		return err
	}
	if _, owned := ownedNamespaces[c.NamespaceID]; !owned {
		return fmt.Errorf("namespace_id %q is not owned by the containing App", c.NamespaceID)
	}
	if c.LocalDependencies == nil || c.ExternalComponentRefs == nil {
		return fmt.Errorf("component dependency arrays must be present, not null")
	}
	if err := validateSortedStrings("local_dependencies", c.LocalDependencies, validateDigest); err != nil {
		return err
	}
	lastRef := ""
	for i, ref := range c.ExternalComponentRefs {
		if err := validateSegment("dependency alias", ref.DependencyAlias); err != nil {
			return fmt.Errorf("external_component_refs[%d]: %w", i, err)
		}
		if _, exists := dependencyAliases[ref.DependencyAlias]; !exists {
			return fmt.Errorf("external_component_refs[%d]: undeclared dependency alias %q", i, ref.DependencyAlias)
		}
		if err := ValidateNamespaceID(ref.NamespaceID); err != nil {
			return fmt.Errorf("external_component_refs[%d]: %w", i, err)
		}
		if err := validateNamedID("component kind", ref.ComponentKind, 2, true); err != nil {
			return fmt.Errorf("external_component_refs[%d]: %w", i, err)
		}
		if err := validateBusinessKey(ref.BusinessKey); err != nil {
			return fmt.Errorf("external_component_refs[%d]: %w", i, err)
		}
		key := externalRefKey(ref)
		if i > 0 && key <= lastRef {
			return fmt.Errorf("external_component_refs must be strictly sorted by their canonical key")
		}
		lastRef = key
	}
	switch c.OverridePolicy {
	case OverridePolicyReplace, OverridePolicyPreserveAuthorized:
	default:
		return fmt.Errorf("unknown override_policy %q", c.OverridePolicy)
	}
	switch c.RemovalPolicy {
	case RemovalPolicyRemoveIfUnreferenced, RemovalPolicyBlockIfReferencedOrOverridden:
	default:
		return fmt.Errorf("unknown removal_policy %q", c.RemovalPolicy)
	}
	return nil
}

// ValidateComponentProjection validates the intrinsic immutable identity of a
// component projected from a verified App package and binds the projected
// behavior bytes to that identity. Package ownership and dependency closure
// remain VerifyPackage work and are intentionally not re-authored here.
func ValidateComponentProjection(c Component, canonicalBehavior []byte) error {
	if err := validateComponentIdentity(c); err != nil {
		return err
	}
	actual, err := ComponentDigest(c.ComponentKind, c.ComponentFormatVersion, canonicalBehavior)
	if err != nil {
		return err
	}
	if actual != c.ContentDigest {
		return fmt.Errorf("content_digest %q does not bind the projected component behavior", c.ContentDigest)
	}
	return nil
}

func validateComponentIdentity(c Component) error {
	if err := validateNamedID("component kind", c.ComponentKind, 2, true); err != nil {
		return err
	}
	if c.ComponentFormatVersion == 0 {
		return fmt.Errorf("component_format_version must be positive")
	}
	if err := ValidateNamespaceID(c.NamespaceID); err != nil {
		return err
	}
	if err := validateBusinessKey(c.BusinessKey); err != nil {
		return err
	}
	if err := validatePositiveDecimal(c.DefinitionVersion); err != nil {
		return fmt.Errorf("definition_version: %w", err)
	}
	expectedID := ComponentID(c.NamespaceID, c.ComponentKind, c.BusinessKey)
	if c.ComponentID != expectedID {
		return fmt.Errorf("component_id %q does not match derived %q", c.ComponentID, expectedID)
	}
	if err := validateDigest(c.ContentDigest); err != nil {
		return fmt.Errorf("content_digest: %w", err)
	}
	return nil
}

func validateNamedID(label, value string, minimumSegments int, versioned bool) error {
	if len(value) == 0 || len(value) > 128 || !isASCII(value) {
		return fmt.Errorf("%s must be 1..128 ASCII bytes", label)
	}
	segments := strings.Split(value, ".")
	if len(segments) < minimumSegments {
		return fmt.Errorf("%s requires at least %d dot segments", label, minimumSegments)
	}
	for _, segment := range segments {
		if err := validateSegment(label, segment); err != nil {
			return err
		}
	}
	if versioned {
		last := segments[len(segments)-1]
		if len(last) < 2 || last[0] != 'v' || last[1] == '0' {
			return fmt.Errorf("%s must end in vN with a positive canonical major", label)
		}
		for _, char := range last[1:] {
			if char < '0' || char > '9' {
				return fmt.Errorf("%s must end in vN with a positive canonical major", label)
			}
		}
	}
	return nil
}

func validateSegment(label, value string) error {
	if len(value) == 0 || len(value) > 63 || value[0] < 'a' || value[0] > 'z' || value[len(value)-1] == '-' {
		return fmt.Errorf("%s contains an invalid canonical segment %q", label, value)
	}
	for _, char := range []byte(value[1:]) {
		if (char < 'a' || char > 'z') && (char < '0' || char > '9') && char != '-' {
			return fmt.Errorf("%s contains an invalid canonical segment %q", label, value)
		}
	}
	return nil
}

func validateSortedStrings(label string, values []string, validate func(string) error) error {
	for i, value := range values {
		if err := validate(value); err != nil {
			return fmt.Errorf("%s[%d]: %w", label, i, err)
		}
		if i > 0 && value <= values[i-1] {
			return fmt.Errorf("%s must be strictly bytewise sorted and duplicate-free", label)
		}
	}
	return nil
}

func validateDigest(value string) error {
	if len(value) != len("sha256:")+64 || !strings.HasPrefix(value, "sha256:") {
		return fmt.Errorf("digest must be sha256:<64 lowercase hex>")
	}
	hexText := value[len("sha256:"):]
	if strings.ToLower(hexText) != hexText {
		return fmt.Errorf("digest hex must be lowercase")
	}
	if _, err := hex.DecodeString(hexText); err != nil {
		return fmt.Errorf("digest hex is invalid")
	}
	return nil
}

func validateBusinessKey(value string) error {
	if value == "" || len(value) > 255 || !utf8.ValidString(value) || strings.TrimSpace(value) != value {
		return fmt.Errorf("business_key must be canonical UTF-8 with 1..255 bytes and no surrounding whitespace")
	}
	for _, r := range value {
		if r < 0x20 || r == 0x7f {
			return fmt.Errorf("business_key cannot contain control characters")
		}
	}
	return nil
}

func validatePositiveDecimal(value string) error {
	if value == "" || value == "0" || (len(value) > 1 && value[0] == '0') {
		return fmt.Errorf("must be a positive canonical uint64 decimal string")
	}
	for _, char := range value {
		if char < '0' || char > '9' {
			return fmt.Errorf("must be a positive canonical uint64 decimal string")
		}
	}
	if _, err := strconv.ParseUint(value, 10, 64); err != nil {
		return fmt.Errorf("must fit uint64")
	}
	return nil
}

func rejectComponentCycles(components []Component) error {
	edges := map[string][]string{}
	for _, component := range components {
		edges[component.ComponentID] = component.LocalDependencies
	}
	const (
		unseen = iota
		visiting
		done
	)
	state := map[string]int{}
	var visit func(string) error
	visit = func(id string) error {
		switch state[id] {
		case visiting:
			return fmt.Errorf("metadata bundle local dependency cycle includes %s", id)
		case done:
			return nil
		}
		state[id] = visiting
		for _, dependency := range edges[id] {
			if err := visit(dependency); err != nil {
				return err
			}
		}
		state[id] = done
		return nil
	}
	for id := range edges {
		if err := visit(id); err != nil {
			return err
		}
	}
	return nil
}

func externalRefKey(ref ExternalComponentRef) string {
	return ref.DependencyAlias + "\x00" + ref.NamespaceID + "\x00" + ref.ComponentKind + "\x00" + ref.BusinessKey
}

func stringSet(values []string) map[string]struct{} {
	result := make(map[string]struct{}, len(values))
	for _, value := range values {
		result[value] = struct{}{}
	}
	return result
}

func isASCII(value string) bool {
	for _, char := range []byte(value) {
		if char >= utf8.RuneSelf {
			return false
		}
	}
	return true
}

func isLowercaseUUIDVersion7(value string) bool {
	if len(value) != 36 || value[8] != '-' || value[13] != '-' || value[18] != '-' || value[23] != '-' || value[14] != '7' {
		return false
	}
	for i, char := range []byte(value) {
		if i == 8 || i == 13 || i == 18 || i == 23 {
			continue
		}
		if (char < '0' || char > '9') && (char < 'a' || char > 'f') {
			return false
		}
	}
	variant := value[19]
	return variant == '8' || variant == '9' || variant == 'a' || variant == 'b'
}
