// Package image provides utilities for defining container image structs and working
// with them.
package image

import (
	"fmt"
	"strings"

	"edge-infra.dev/pkg/lib/errors"

	"github.com/google/go-containerregistry/pkg/crane"
)

// Image defines information we care about for a given container image.
// `yaml` tags used so the struct can be correctly marshaled by Kpt
type Image struct {
	// Digest is the sha256 digest for the image
	Digest string `yaml:"digest"`
	// Registry is the container registry string, e.g., us.gcr.io,
	// ncr-emeraldedge-docker-dev.jfrog.io.
	// https://docs.microsoft.com/en-us/azure/container-registry/container-registry-concepts#registry
	Registry string `yaml:"registry,omitempty"`
	// Repository is the container name, e.g., talaria.  They can also contain slashes,
	// used to implement namespacing in some registries, such as GCR.
	// For GCR, the repository would be $project-id/talaria by default, but they
	// can be more deeply nested.
	// Multiple namespaces can also be used:
	// https://docs.microsoft.com/en-us/azure/container-registry/container-registry-concepts#repository
	Repository string `yaml:"repository"`
}

// Reference creates the fully qualified image reference string for an Image based
// on the digest, e.g. us-east1-docker.pkg.dev/ret-edge-pltf-infra/workloads/talaria@sha256:...
func (img *Image) Reference() string {
	return fmt.Sprintf("%s/%s@%s", img.Registry, img.Repository, img.Digest)
}

// Name returns the colloquial container image name, e.g., `talaria`.
//
// Useful for pulling out a consistent image identifier regardless of the
// repository.
// For example, say we need to identify container image references for the same
// container image across multiple registries, GCR and JFrog.  Since GCR
// supports repository namespacing (and enforces you namespace by your $project-id),
// simply comparing the repository will not work ($project-id/talaria wont match talaria).
//
// Note that the name is a substring of the Image.Repository.  If the
// registry implementation supports repository namespacing, then it will
// be the last token after splitting on `/` and removing the tag/digest.
// If the registry implementation does _not_ support repository namespacing,
// the repository is the container image name.
func (img *Image) Name() string {
	if strings.Contains(img.Repository, "/") {
		return NameFromRef(img.Repository)
	}
	return img.Repository
}

// NameFromRef takes in a full container image reference string, such as
// us-east1-docker.pkg.dev/ret-edge-pltf-infra/workloads/talaria:dev or gcr.io/edge/talaria@sha256:...
// and returns the image name.  A very generous function which will return "" if
// it cannot find a match.
func NameFromRef(ref string) string {
	if strings.Contains(ref, "@") {
		tokens := strings.Split(ref, "@")
		tokens = strings.Split(tokens[0], "/")
		return tokens[len(tokens)-1]
	}
	if strings.Contains(ref, ":") {
		// no @ means we are dealing with vanilla tag, can split on ':'
		tokens := strings.Split(ref, ":")
		tokens = strings.Split(tokens[0], "/")
		return tokens[len(tokens)-1]
	}

	// no @ or : means its not really a ref, we'll try anyway and return
	// the string after the final '/'
	tokens := strings.Split(ref, "/")
	// return empty string if we can't even do that
	if len(tokens) == 0 {
		return ""
	}
	return tokens[len(tokens)-1]
}

// TagImage tags an individual image with all passed in tags
func TagImage(img Image, tags []string) error {
	for _, tag := range tags {
		ref := img.Reference()
		if err := crane.Tag(ref, tag); err != nil {
			return errors.New(fmt.Sprintf("failed to tag %s with %s", ref, tag), err)
		}
	}
	return nil
}