package curatedpackages

import (
	"context"
	"fmt"
	"sort"
	"strings"

	"sigs.k8s.io/yaml"

	packagesv1 "github.com/aws/eks-anywhere-packages/api/v1alpha1"
	"github.com/aws/eks-anywhere/pkg/logger"
	"github.com/aws/eks-anywhere/pkg/registry"
	releasev1 "github.com/aws/eks-anywhere/release/api/v1alpha1"
)

type PackageReader struct {
	cache           *registry.Cache
	credentialStore *registry.CredentialStore
	awsRegion       string
}

// NewPackageReader create a new package reader with storage client.
func NewPackageReader(cache *registry.Cache, credentialStore *registry.CredentialStore, awsRegion string) *PackageReader {
	if len(awsRegion) <= 0 {
		awsRegion = eksaDefaultRegion
	}
	return &PackageReader{
		cache:           cache,
		credentialStore: credentialStore,
		awsRegion:       awsRegion,
	}
}

// ReadImagesFromBundles and return a list of image artifacts.
func (r *PackageReader) ReadImagesFromBundles(ctx context.Context, b *releasev1.Bundles) ([]registry.Artifact, error) {
	var err error
	var images []registry.Artifact
	for _, vb := range b.Spec.VersionsBundles {
		bundleURI, bundle, err := r.getBundle(ctx, vb)
		if err != nil {
			logger.Info("Warning: Failed getting bundle reference", "error", err)
			continue
		}
		packageImages := r.fetchImagesFromBundle(bundleURI, bundle)
		images = append(images, packageImages...)
	}

	return removeDuplicateImages(images), err
}

// ReadChartsFromBundles and return a list of chart artifacts.
func (r *PackageReader) ReadChartsFromBundles(ctx context.Context, b *releasev1.Bundles) []registry.Artifact {
	var images []registry.Artifact
	for _, vb := range b.Spec.VersionsBundles {
		bundleURI, bundle, err := r.getBundle(ctx, vb)
		if err != nil {
			logger.Info("Warning: Failed getting bundle reference", "error", err)
			continue
		}
		bundleArtifact := registry.NewArtifactFromURI(bundleURI)
		images = append(images, bundleArtifact)
		packagesHelmChart := r.fetchPackagesHelmChart(bundleURI, bundle)
		images = append(images, packagesHelmChart...)
	}
	return removeDuplicateImages(images)
}

func (r *PackageReader) getBundle(ctx context.Context, vb releasev1.VersionsBundle) (string, *packagesv1.PackageBundle, error) {
	bundleURI, err := GetPackageBundleRef(vb)
	if err != nil {
		return "", nil, err
	}

	artifact := registry.NewArtifactFromURI(bundleURI)
	sc, err := r.cache.Get(registry.NewStorageContext(artifact.Registry, r.credentialStore, nil, false))
	if err != nil {
		return "", nil, err
	}

	data, err := registry.PullBytes(ctx, sc, artifact)
	if err != nil {
		return "", nil, err
	}
	bundle := packagesv1.PackageBundle{}
	err = yaml.Unmarshal(data, &bundle)
	if err != nil {
		return "", nil, err
	}
	return artifact.VersionedImage(), &bundle, nil
}

func (r *PackageReader) fetchPackagesHelmChart(bundleURI string, bundle *packagesv1.PackageBundle) []registry.Artifact {
	images := make([]registry.Artifact, 0, len(bundle.Spec.Packages))
	bundleRegistry := getChartRegistry(bundleURI)
	for _, p := range bundle.Spec.Packages {
		chartURI := fmt.Sprintf("%s/%s@%s", bundleRegistry, p.Source.Repository, p.Source.Versions[0].Digest)
		pHC := registry.NewArtifactFromURI(chartURI)
		pHC.Tag = p.Source.Versions[0].Name
		images = append(images, pHC)
	}
	return images
}

func (r *PackageReader) fetchImagesFromBundle(bundleURI string, bundle *packagesv1.PackageBundle) []registry.Artifact {
	images := make([]registry.Artifact, 0, len(bundle.Spec.Packages))
	bundleRegistry := getImageRegistry(bundleURI, r.awsRegion)
	for _, p := range bundle.Spec.Packages {
		// each package will have at least one version
		for _, version := range p.Source.Versions[0].Images {
			imageURI := fmt.Sprintf("%s/%s@%s", bundleRegistry, version.Repository, version.Digest)
			image := registry.NewArtifactFromURI(imageURI)
			image.Tag = "" // We do not have the tag right now
			images = append(images, image)
		}
	}
	return images
}

func removeDuplicateImages(images []registry.Artifact) []registry.Artifact {
	uniqueImages := make(map[string]struct{})
	var list []registry.Artifact
	for _, item := range images {
		if _, value := uniqueImages[item.VersionedImage()]; !value {
			uniqueImages[item.VersionedImage()] = struct{}{}
			list = append(list, item)
		}
	}
	sort.Slice(list, func(i, j int) bool {
		return list[i].VersionedImage() < list[j].VersionedImage()
	})
	return list
}

func getChartRegistry(uri string) string {
	if strings.Contains(uri, prodPublicRegistryURI) {
		return prodPublicRegistryURI
	}
	if strings.Contains(uri, stagingPublicRegistryURI) {
		return stagingPublicRegistryURI
	}
	return devRegionalPublicRegistryURI
}

func getImageRegistry(uri, awsRegion string) string {
	if strings.Contains(uri, prodPublicRegistryURI) {
		return strings.ReplaceAll(prodNonRegionalPrivateRegistryURI, eksaDefaultRegion, awsRegion)
	}
	if strings.Contains(uri, stagingPublicRegistryURI) {
		return strings.ReplaceAll(stagingRegionalPrivateRegistryURI, eksaDefaultRegion, awsRegion)
	}
	return strings.ReplaceAll(devRegionalPrivateRegistryURI, eksaDefaultRegion, awsRegion)
}
