package layoutbundleslotv1

import (
	"fmt"
	"strconv"

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

// VerifyPackageDeclarations first verifies the complete App package and then
// runs the registered layout-bundle-slot component-kind validator. It returns
// every verified slot declaration in canonical bundle component order.
func VerifyPackageDeclarations(pkg appauthority.Package) (appauthority.VerifiedPackage, []Declaration, error) {
	verified, err := appauthority.VerifyPackage(pkg)
	if err != nil {
		return appauthority.VerifiedPackage{}, nil, err
	}
	componentsByID := make(map[string]appauthority.Component, len(verified.Bundle.Components))
	for _, component := range verified.Bundle.Components {
		componentsByID[component.ComponentID] = component
	}
	declarations := make([]Declaration, 0)
	seenKeys := map[layoutbundlekeyv1.LocalComponentKey]string{}
	for index, component := range verified.Bundle.Components {
		if component.ComponentKind != ComponentKind {
			continue
		}
		definitionVersion, descriptorErr := validateSlotDescriptor(component)
		if descriptorErr != nil {
			return appauthority.VerifiedPackage{}, nil, fmt.Errorf("layout-bundle slot component[%d]: %w", index, descriptorErr)
		}
		behavior, parseErr := ParseCanonicalBehavior(verified.ComponentBlobs[index].CanonicalBehavior)
		if parseErr != nil {
			return appauthority.VerifiedPackage{}, nil, fmt.Errorf("layout-bundle slot component[%d]: %w", index, parseErr)
		}
		localKey := layoutbundlekeyv1.LocalComponentKey(component.BusinessKey)
		if behavior.LocalComponentKey != localKey {
			return appauthority.VerifiedPackage{}, nil, fmt.Errorf("layout-bundle slot component[%d]: behavior local_component_key does not match descriptor business_key", index)
		}
		if existing, duplicate := seenKeys[localKey]; duplicate {
			return appauthority.VerifiedPackage{}, nil, fmt.Errorf("layout-bundle slot component[%d]: LocalComponentKey is already declared by %s", index, existing)
		}
		seenKeys[localKey] = component.ComponentID
		if component.LocalDependencies[0] != behavior.TargetComponentID {
			return appauthority.VerifiedPackage{}, nil, fmt.Errorf("layout-bundle slot component[%d]: singleton local dependency does not match target_component_id", index)
		}
		target, exists := componentsByID[behavior.TargetComponentID]
		if !exists {
			return appauthority.VerifiedPackage{}, nil, fmt.Errorf("layout-bundle slot component[%d]: target_component_id does not name a component in the same MetadataBundle", index)
		}
		if target.ComponentKind == ComponentKind {
			return appauthority.VerifiedPackage{}, nil, fmt.Errorf("layout-bundle slot component[%d]: target_component_id must name a non-slot component", index)
		}
		if definitionVersion == 1 && behavior.PreviousTargetComponentID != nil {
			return appauthority.VerifiedPackage{}, nil, fmt.Errorf("layout-bundle slot component[%d]: definition_version 1 must omit previous_target_component_id", index)
		}
		if definitionVersion > 1 && behavior.PreviousTargetComponentID == nil {
			return appauthority.VerifiedPackage{}, nil, fmt.Errorf("layout-bundle slot component[%d]: definition_version above 1 requires previous_target_component_id", index)
		}
		revisionDigest, digestErr := LayoutBundleSlotRevisionDigest(verified.Manifest.AppID, component)
		if digestErr != nil {
			return appauthority.VerifiedPackage{}, nil, fmt.Errorf("layout-bundle slot component[%d]: %w", index, digestErr)
		}
		declarations = append(declarations, Declaration{
			AppID:                     verified.Manifest.AppID,
			NamespaceID:               component.NamespaceID,
			LocalComponentKey:         localKey,
			DeclarationComponentID:    component.ComponentID,
			DefinitionVersion:         definitionVersion,
			TargetComponentID:         behavior.TargetComponentID,
			PreviousTargetComponentID: cloneStringPointer(behavior.PreviousTargetComponentID),
			BehaviorDigest:            component.ContentDigest,
			RevisionDigest:            revisionDigest,
		})
	}
	return verified, declarations, nil
}

func validateSlotDescriptor(component appauthority.Component) (uint64, error) {
	if component.ComponentKind != ComponentKind {
		return 0, fmt.Errorf("component_kind must equal %q", ComponentKind)
	}
	if component.ComponentFormatVersion != ComponentFormatVersion {
		return 0, fmt.Errorf("component_format_version must equal %d", ComponentFormatVersion)
	}
	if err := appauthority.ValidateNamespaceID(component.NamespaceID); err != nil {
		return 0, fmt.Errorf("namespace_id: %w", err)
	}
	localKey := layoutbundlekeyv1.LocalComponentKey(component.BusinessKey)
	if err := layoutbundlekeyv1.ValidateLocalComponentKey(localKey); err != nil {
		return 0, fmt.Errorf("business_key: %w", err)
	}
	expectedID := appauthority.ComponentID(component.NamespaceID, ComponentKind, component.BusinessKey)
	if component.ComponentID != expectedID {
		return 0, fmt.Errorf("component_id does not match the derived slot declaration identity")
	}
	definitionVersion, err := parseDefinitionVersion(component.DefinitionVersion)
	if err != nil {
		return 0, fmt.Errorf("definition_version: %w", err)
	}
	if component.LocalDependencies == nil || len(component.LocalDependencies) != 1 {
		return 0, fmt.Errorf("local_dependencies must be a present singleton target array")
	}
	if err := appauthority.ValidateDigest(component.LocalDependencies[0]); err != nil {
		return 0, fmt.Errorf("local_dependencies target: %w", err)
	}
	if component.ExternalComponentRefs == nil || len(component.ExternalComponentRefs) != 0 {
		return 0, fmt.Errorf("external_component_refs must be present and empty")
	}
	if component.OverridePolicy != appauthority.OverridePolicyReplace {
		return 0, fmt.Errorf("override_policy must equal %q", appauthority.OverridePolicyReplace)
	}
	if component.RemovalPolicy != appauthority.RemovalPolicyBlockIfReferencedOrOverridden {
		return 0, fmt.Errorf("removal_policy must equal %q", appauthority.RemovalPolicyBlockIfReferencedOrOverridden)
	}
	if err := appauthority.ValidateDigest(component.ContentDigest); err != nil {
		return 0, fmt.Errorf("content_digest: %w", err)
	}
	return definitionVersion, nil
}

func parseDefinitionVersion(value string) (uint64, error) {
	parsed, err := strconv.ParseUint(value, 10, 64)
	if err != nil || parsed == 0 || strconv.FormatUint(parsed, 10) != value {
		return 0, fmt.Errorf("must be a positive canonical uint64 decimal string")
	}
	return parsed, nil
}
