package validate

import (
	"fmt"

	v1 "github.com/google/go-containerregistry/pkg/v1"

	wh "edge-infra.dev/pkg/f8n/warehouse"
	"edge-infra.dev/pkg/f8n/warehouse/cluster"
	"edge-infra.dev/pkg/f8n/warehouse/oci"
	"edge-infra.dev/pkg/f8n/warehouse/oci/layer"
	"edge-infra.dev/pkg/f8n/warehouse/pallet"
)

// Validate a Pallet artifact meets the OCI, Warehouse and Pallet specifications.
func Pallet(p pallet.Pallet) error {
	// validate as a generic Warehouse artifact first
	if err := Warehouse(p); err != nil {
		return err
	}

	// perform specific Pallet validation
	err := Validate(p, &Fns{
		Index: palletValidateIndex,
		Image: palletValidateImage,
		Layer: palletValidateLayer,
	})
	if err != nil {
		return fmt.Errorf("invalid Pallet artifact: %v", err)
	}
	return nil
}

func palletValidateIndex(idx oci.Artifact, rootAnnotations map[string]string) error {
	annotations, err := oci.Annotations(idx)
	if err != nil {
		return err
	}

	// check top-level annotations are as expected for pallet artifact
	if err := Annotations(annotations, palletAnnotationValidators, palletOptionalAnnotationValidators); err != nil {
		return err
	}

	// check annotations values specified by image are present in root node
	if err := validatePalletAgainstRoot(annotations, rootAnnotations); err != nil {
		return err
	}

	idxName := annotations[wh.AnnotationName]
	palletClusterProviders, err := cluster.ProvidersFromAnnotations(annotations)
	if err != nil {
		return err
	}

	i := idx.(v1.ImageIndex)
	idxManifest, err := i.IndexManifest()
	if err != nil {
		return err
	}

	// check each descriptor expected annotations for pallet manifest
	for n, manifest := range idxManifest.Manifests {
		if err := validatePalletManifest(manifest, idxName, palletClusterProviders); err != nil {
			return fmt.Errorf("manifest %d is invalid: %v", n, err)
		}
	}

	return nil
}

func validatePalletManifest(manifest v1.Descriptor, idxName string, palletClusterProviders cluster.Providers) error {
	annotations := manifest.Annotations

	// check descriptor has expected annotations for Warehouse manifest
	if err := Annotations(annotations, palletManifestAnnotationValidators, nil); err != nil {
		return err
	}

	// if descriptor is a dependency, we are done
	if annotations[wh.AnnotationRefName] != idxName {
		return nil
	}

	// provider images should only have providers which match those in the pallet
	clusterProviders, err := cluster.ProvidersFromAnnotations(annotations)
	if err != nil {
		return err
	}
	for _, provider := range clusterProviders {
		if err := palletClusterProviders.IsValid(provider); err != nil {
			return err
		}
	}

	return nil
}

func palletValidateImage(img oci.Artifact, rootAnnotations map[string]string) error {
	annotations, err := oci.Annotations(img)
	if err != nil {
		return err
	}

	// check top-level annotations are as expected for Pallet artifact
	if err := Annotations(annotations, palletAnnotationValidators, palletOptionalAnnotationValidators); err != nil {
		return err
	}

	// check annotations values specified by image are present in root node
	if err := validatePalletAgainstRoot(annotations, rootAnnotations); err != nil {
		return err
	}

	i := img.(v1.Image)
	layers, err := layer.FromImage(i)
	if err != nil {
		return err
	}

	numBaseLayers := 0
	numInfraLayers := 0
	for _, l := range layers {
		layerAnnotations := l.Annotations()
		switch l.Type() {
		case layer.Infra:
			numInfraLayers++
		case layer.Runtime:
			if _, ok := layerAnnotations[wh.AnnotationLayerRuntimeCapability]; !ok {
				numBaseLayers++
			}
		}
	}

	// check there is a maximum of one base runtime layer, others must be runtime capabilities
	if numBaseLayers > 1 {
		return NewImageLayersError(errMultipleBaseLayers, numBaseLayers)
	}

	// check there is a maximum of one infrastructure layer
	if numInfraLayers > 1 {
		return NewImageLayersError(errMultipleInfraLayers, numInfraLayers)
	}

	return nil
}

func palletValidateLayer(l layer.Layer, _ map[string]string) error {
	// check we can convert the layer into unstructured K8s objects
	if _, err := l.Unstructured(); err != nil {
		return fmt.Errorf("%v: %v", errLayersMustBeK8sManifests, err)
	}

	return nil
}