package registry

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/porter-dev/porter/internal/telemetry"

	artifactregistry "cloud.google.com/go/artifactregistry/apiv1beta2"
	"connectrpc.com/connect"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ecr"
	porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
	"github.com/porter-dev/porter/api/server/shared/config"
	"github.com/porter-dev/porter/internal/models"
	"github.com/porter-dev/porter/internal/oauth"
	"github.com/porter-dev/porter/internal/repository"
	"golang.org/x/oauth2"
	v1artifactregistry "google.golang.org/api/artifactregistry/v1"
	"google.golang.org/api/iterator"
	"google.golang.org/api/option"
	artifactregistrypb "google.golang.org/genproto/googleapis/devtools/artifactregistry/v1beta2"

	ints "github.com/porter-dev/porter/internal/models/integrations"

	ptypes "github.com/porter-dev/porter/api/types"

	"github.com/digitalocean/godo"
	"github.com/docker/cli/cli/config/configfile"
	"github.com/docker/cli/cli/config/types"
	"github.com/docker/distribution/reference"

	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
)

// Registry wraps the gorm Registry model
type Registry models.Registry

func GetECRRegistryURL(awsIntRepo repository.AWSIntegrationRepository, projectID, awsIntID uint) (string, error) {
	awsInt, err := awsIntRepo.ReadAWSIntegration(projectID, awsIntID)
	if err != nil {
		return "", err
	}

	sess, err := awsInt.GetSession()
	if err != nil {
		return "", err
	}

	ecrSvc := ecr.New(sess)

	output, err := ecrSvc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
	if err != nil {
		return "", err
	}

	return *output.AuthorizationData[0].ProxyEndpoint, nil
}

// ListRepositories lists the repositories for a registry
func (r *Registry) ListRepositories(
	ctx context.Context,
	repo repository.Repository,
	conf *config.Config,
) ([]*ptypes.RegistryRepository, error) {
	ctx, span := telemetry.NewSpan(ctx, "list-repositories")
	defer span.End()

	telemetry.WithAttributes(span,
		telemetry.AttributeKV{Key: "registry-name", Value: r.Name},
		telemetry.AttributeKV{Key: "registry-id", Value: r.ID},
		telemetry.AttributeKV{Key: "project-id", Value: r.ProjectID},
	)

	// switch on the auth mechanism to get a token
	if r.AWSIntegrationID != 0 {
		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "auth-mechanism", Value: "aws"})
		aws, err := repo.AWSIntegration().ReadAWSIntegration(
			r.ProjectID,
			r.AWSIntegrationID,
		)
		if err != nil {
			return nil, telemetry.Error(ctx, span, err, "error reading aws integration")
		}

		repos, err := r.listECRRepositories(aws)
		if err != nil {
			return nil, telemetry.Error(ctx, span, err, "error listing ecr repositories")
		}

		return repos, nil
	}

	if r.GCPIntegrationID != 0 {
		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "auth-mechanism", Value: "gcp"})
		if strings.Contains(r.URL, "pkg.dev") {
			return r.listGARRepositories(ctx, repo)
		}

		repos, err := r.listGCRRepositories(repo)
		if err != nil {
			return nil, telemetry.Error(ctx, span, err, "error listing gcr repositories")
		}

		return repos, nil
	}

	if r.DOIntegrationID != 0 {
		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "auth-mechanism", Value: "do"})

		repos, err := r.listDOCRRepositories(repo, conf.DOConf)
		if err != nil {
			return nil, telemetry.Error(ctx, span, err, "error listing docr repositories")
		}

		return repos, nil
	}

	if r.AzureIntegrationID != 0 {
		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "auth-mechanism", Value: "azure"})

		repos, err := r.listACRRepositories(ctx, repo)
		if err != nil {
			return nil, telemetry.Error(ctx, span, err, "error listing acr repositories")
		}

		return repos, nil
	}

	if r.BasicIntegrationID != 0 {
		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "auth-mechanism", Value: "basic"})

		repos, err := r.listPrivateRegistryRepositories(repo)
		if err != nil {
			return nil, telemetry.Error(ctx, span, err, "error listing private repositories")
		}

		return repos, nil
	}

	project, err := conf.Repo.Project().ReadProject(r.ProjectID)
	if err != nil {
		return nil, telemetry.Error(ctx, span, err, "error getting project for repository")
	}

	if project.CapiProvisionerEnabled {
		// TODO: Remove this conditional when AWS list repos is supported in CCP
		if strings.Contains(r.URL, ".azurecr.") {
			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "auth-mechanism", Value: "capi-azure"})

			req := connect.NewRequest(&porterv1.ListRepositoriesForRegistryRequest{
				ProjectId:   int64(r.ProjectID),
				RegistryUri: r.URL,
			})

			resp, err := conf.ClusterControlPlaneClient.ListRepositoriesForRegistry(ctx, req)
			if err != nil {
				return nil, telemetry.Error(ctx, span, err, "error listing ecr repositories")
			}

			res := make([]*ptypes.RegistryRepository, 0)

			parsedURL, err := url.Parse("https://" + r.URL)
			if err != nil {
				return nil, telemetry.Error(ctx, span, err, "error parsing url")
			}

			for _, repo := range resp.Msg.Repositories {
				res = append(res, &ptypes.RegistryRepository{
					Name: repo.Name,
					URI:  parsedURL.Host + "/" + repo.Name,
				})
			}

			return res, nil
		} else {
			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "auth-mechanism", Value: "capi-aws"})
			uri := strings.TrimPrefix(r.URL, "https://")
			splits := strings.Split(uri, ".")
			if len(splits) < 4 {
				return nil, telemetry.Error(ctx, span, nil, "uri does not have enough splits")
			}
			accountID := splits[0]
			region := splits[3]
			req := connect.NewRequest(&porterv1.AssumeRoleCredentialsRequest{
				ProjectId:    int64(r.ProjectID),
				AwsAccountId: accountID,
			})
			creds, err := conf.ClusterControlPlaneClient.AssumeRoleCredentials(ctx, req)
			if err != nil {
				return nil, telemetry.Error(ctx, span, err, "error getting capi credentials for registry")
			}
			aws := &ints.AWSIntegration{
				AWSAccessKeyID:     []byte(creds.Msg.AwsAccessId),
				AWSSecretAccessKey: []byte(creds.Msg.AwsSecretKey),
				AWSSessionToken:    []byte(creds.Msg.AwsSessionToken),
				AWSRegion:          region,
			}

			repos, err := r.listECRRepositories(aws)
			if err != nil {
				return nil, telemetry.Error(ctx, span, err, "error listing ecr repositories")
			}

			return repos, nil
		}
	}

	return nil, telemetry.Error(ctx, span, nil, "error listing repositories")
}

type gcrJWT struct {
	AccessToken  string `json:"token"`
	ExpiresInSec int    `json:"expires_in"`
}

type gcrErr struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

type gcrRepositoryResp struct {
	Repositories []string `json:"repositories"`
	Errors       []gcrErr `json:"errors"`
}

func (r *Registry) GetGCRToken(ctx context.Context, repo repository.Repository) (*oauth2.Token, error) {
	getTokenCache := r.getTokenCacheFunc(ctx, repo)

	gcp, err := repo.GCPIntegration().ReadGCPIntegration(
		r.ProjectID,
		r.GCPIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	// get oauth2 access token
	return gcp.GetBearerToken(
		ctx,
		getTokenCache,
		r.setTokenCacheFunc(ctx, repo),
		"https://www.googleapis.com/auth/devstorage.read_write",
	)
}

func (r *Registry) listGCRRepositories(
	repo repository.Repository,
) ([]*ptypes.RegistryRepository, error) {
	gcp, err := repo.GCPIntegration().ReadGCPIntegration(
		r.ProjectID,
		r.GCPIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	// Just use service account key to authenticate, since scopes may not be in place
	// for oauth. This also prevents us from making more requests.
	client := &http.Client{}

	regURL := r.URL

	if !strings.HasPrefix(regURL, "http") {
		regURL = fmt.Sprintf("https://%s", regURL)
	}

	regURLParsed, err := url.Parse(regURL)
	regHostname := "gcr.io"

	if err == nil {
		regHostname = regURLParsed.Host
	}

	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("https://%s/v2/_catalog", regHostname),
		nil,
	)
	if err != nil {
		return nil, err
	}

	req.SetBasicAuth("_json_key", string(gcp.GCPKeyData))

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	gcrResp := gcrRepositoryResp{}

	if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
		return nil, fmt.Errorf("Could not read GCR repositories: %v", err)
	}

	if len(gcrResp.Errors) > 0 {
		errMsg := ""
		for _, gcrErr := range gcrResp.Errors {
			errMsg += fmt.Sprintf(": Code %s, message %s", gcrErr.Code, gcrErr.Message)
		}

		return nil, fmt.Errorf(errMsg)
	}

	res := make([]*ptypes.RegistryRepository, 0)

	parsedURL, err := url.Parse("https://" + r.URL)
	if err != nil {
		return nil, err
	}

	for _, repo := range gcrResp.Repositories {
		res = append(res, &ptypes.RegistryRepository{
			Name: repo,
			URI:  parsedURL.Host + "/" + repo,
		})
	}

	return res, nil
}

func (r *Registry) GetGARToken(ctx context.Context, repo repository.Repository) (*oauth2.Token, error) {
	getTokenCache := r.getTokenCacheFunc(ctx, repo)

	gcp, err := repo.GCPIntegration().ReadGCPIntegration(
		r.ProjectID,
		r.GCPIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	// get oauth2 access token
	return gcp.GetBearerToken(
		ctx,
		getTokenCache,
		r.setTokenCacheFunc(ctx, repo),
		"https://www.googleapis.com/auth/cloud-platform",
	)
}

type garTokenSource struct {
	// ctx is only passed in here as the oauth2.Token() doesnt support contexts
	ctx  context.Context
	reg  *Registry
	repo repository.Repository
}

func (source *garTokenSource) Token() (*oauth2.Token, error) {
	return source.reg.GetGARToken(source.ctx, source.repo)
}

// GAR has the concept of a "repository" which is a collection of images, unlike ECR or others
// where a repository is a single image. This function returns the list of fully qualified names
// of GAR images including their repository names.
func (r *Registry) listGARRepositories(
	ctx context.Context,
	repo repository.Repository,
) ([]*ptypes.RegistryRepository, error) {
	gcpInt, err := repo.GCPIntegration().ReadGCPIntegration(
		r.ProjectID,
		r.GCPIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	client, err := artifactregistry.NewClient(ctx, option.WithTokenSource(&garTokenSource{
		reg:  r,
		repo: repo,
		ctx:  ctx,
	}), option.WithScopes("roles/artifactregistry.reader"))
	if err != nil {
		return nil, err
	}

	var repoNames []string
	nextToken := ""

	parsedURL, err := url.Parse("https://" + r.URL)
	if err != nil {
		return nil, err
	}

	location := strings.TrimSuffix(parsedURL.Host, "-docker.pkg.dev")

	for {
		it := client.ListRepositories(context.Background(), &artifactregistrypb.ListRepositoriesRequest{
			Parent:    fmt.Sprintf("projects/%s/locations/%s", gcpInt.GCPProjectID, location),
			PageSize:  1000,
			PageToken: nextToken,
		})

		for {
			resp, err := it.Next()

			if err == iterator.Done {
				break
			} else if err != nil {
				return nil, err
			}

			if resp.GetFormat() == artifactregistrypb.Repository_DOCKER { // we only care about
				repoSlice := strings.Split(resp.GetName(), "/")
				repoName := repoSlice[len(repoSlice)-1]

				repoNames = append(repoNames, repoName)
			}
		}

		if it.PageInfo().Token == "" {
			break
		}

		nextToken = it.PageInfo().Token
	}

	svc, err := v1artifactregistry.NewService(ctx, option.WithTokenSource(&garTokenSource{
		reg:  r,
		repo: repo,
		ctx:  ctx,
	}), option.WithScopes("roles/artifactregistry.reader"))
	if err != nil {
		return nil, err
	}

	nextToken = ""

	dockerSvc := v1artifactregistry.NewProjectsLocationsRepositoriesDockerImagesService(svc)

	var (
		wg     sync.WaitGroup
		resMap sync.Map
	)

	for _, repoName := range repoNames {
		wg.Add(1)

		go func(repoName string) {
			defer wg.Done()

			for {
				resp, err := dockerSvc.List(fmt.Sprintf("projects/%s/locations/%s/repositories/%s",
					gcpInt.GCPProjectID, location, repoName)).PageSize(1000).PageToken(nextToken).Do()
				if err != nil {
					// FIXME: we should report this error using a channel
					return
				}

				for _, image := range resp.DockerImages {
					named, err := reference.ParseNamed(image.Uri)
					if err != nil {
						// let us skip this image becaue it has a malformed URI coming from the GCP API
						continue
					}

					uploadTime, _ := time.Parse(time.RFC3339, image.UploadTime)

					resMap.Store(named.Name(), &ptypes.RegistryRepository{
						Name:      repoName,
						URI:       named.Name(),
						CreatedAt: uploadTime,
					})
				}

				if resp.NextPageToken == "" {
					break
				}

				nextToken = resp.NextPageToken
			}
		}(repoName)
	}

	wg.Wait()

	var res []*ptypes.RegistryRepository

	resMap.Range(func(_, value any) bool {
		res = append(res, value.(*ptypes.RegistryRepository))
		return true
	})

	return res, nil
}

func (r *Registry) listECRRepositories(aws *ints.AWSIntegration) ([]*ptypes.RegistryRepository, error) {
	sess, err := aws.GetSession()
	if err != nil {
		return nil, err
	}

	svc := ecr.New(sess)

	resp, err := svc.DescribeRepositories(&ecr.DescribeRepositoriesInput{})
	if err != nil {
		return nil, err
	}

	res := make([]*ptypes.RegistryRepository, 0)

	for _, repo := range resp.Repositories {
		res = append(res, &ptypes.RegistryRepository{
			Name:      *repo.RepositoryName,
			CreatedAt: *repo.CreatedAt,
			URI:       *repo.RepositoryUri,
		})
	}

	return res, nil
}

type acrRepositoryResp struct {
	Repositories []string `json:"repositories"`
}

func (r *Registry) listACRRepositories(ctx context.Context, repo repository.Repository) ([]*ptypes.RegistryRepository, error) {
	ctx, span := telemetry.NewSpan(ctx, "list-acr-repositories")
	defer span.End()

	telemetry.WithAttributes(span,
		telemetry.AttributeKV{Key: "registry-name", Value: r.Name},
		telemetry.AttributeKV{Key: "registry-id", Value: r.ID},
		telemetry.AttributeKV{Key: "project-id", Value: r.ProjectID},
	)

	az, err := repo.AzureIntegration().ReadAzureIntegration(
		r.ProjectID,
		r.AzureIntegrationID,
	)
	if err != nil {
		return nil, telemetry.Error(ctx, span, err, "error reading azure integration")
	}

	client := &http.Client{}

	acrURL := r.URL
	if !strings.Contains(acrURL, "http") {
		acrURL = fmt.Sprintf("https://%s", acrURL)
	}

	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("%s/v2/_catalog", acrURL),
		nil,
	)
	if err != nil {
		return nil, telemetry.Error(ctx, span, err, "error getting http request")
	}

	req.SetBasicAuth(az.AzureClientID, string(az.ServicePrincipalSecret))

	resp, err := client.Do(req)
	if err != nil {
		return nil, telemetry.Error(ctx, span, err, "error making http call")
	}

	acrResp := acrRepositoryResp{}

	if err := json.NewDecoder(resp.Body).Decode(&acrResp); err != nil {
		return nil, telemetry.Error(ctx, span, err, "could not read Azure registry repository response")
	}

	res := make([]*ptypes.RegistryRepository, 0)

	for _, repo := range acrResp.Repositories {
		res = append(res, &ptypes.RegistryRepository{
			Name: repo,
			URI:  strings.TrimPrefix(r.URL, "https://") + "/" + repo,
		})
	}

	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "repo-count", Value: len(acrResp.Repositories)})

	return res, nil
}

// Returns the username/password pair for the registry
func (r *Registry) GetACRCredentials(repo repository.Repository) (string, string, error) {
	az, err := repo.AzureIntegration().ReadAzureIntegration(
		r.ProjectID,
		r.AzureIntegrationID,
	)
	if err != nil {
		return "", "", err
	}

	// if the passwords and name aren't set, generate them
	if az.ACRTokenName == "" || len(az.ACRPassword1) == 0 {
		az.ACRTokenName = "porter-acr-token"

		// create an acr repo token
		cred, err := azidentity.NewClientSecretCredential(az.AzureTenantID, az.AzureClientID, string(az.ServicePrincipalSecret), nil)
		if err != nil {
			return "", "", err
		}

		scopeMapsClient, err := armcontainerregistry.NewScopeMapsClient(az.AzureSubscriptionID, cred, nil)
		if err != nil {
			return "", "", err
		}

		smRes, err := scopeMapsClient.Get(
			context.Background(),
			az.ACRResourceGroupName,
			az.ACRName,
			"_repositories_admin",
			nil,
		)
		if err != nil {
			return "", "", err
		}

		tokensClient, err := armcontainerregistry.NewTokensClient(az.AzureSubscriptionID, cred, nil)
		if err != nil {
			return "", "", err
		}

		pollerResp, err := tokensClient.BeginCreate(
			context.Background(),
			az.ACRResourceGroupName,
			az.ACRName,
			"porter-acr-token",
			armcontainerregistry.Token{
				Properties: &armcontainerregistry.TokenProperties{
					ScopeMapID: smRes.ID,
					Status:     to.Ptr(armcontainerregistry.TokenStatusEnabled),
				},
			},
			nil,
		)
		if err != nil {
			return "", "", err
		}

		tokResp, err := pollerResp.PollUntilDone(context.Background(), 2*time.Second)
		if err != nil {
			return "", "", err
		}

		registriesClient, err := armcontainerregistry.NewRegistriesClient(az.AzureSubscriptionID, cred, nil)
		if err != nil {
			return "", "", err
		}

		poller, err := registriesClient.BeginGenerateCredentials(
			context.Background(),
			az.ACRResourceGroupName,
			az.ACRName,
			armcontainerregistry.GenerateCredentialsParameters{
				TokenID: tokResp.ID,
			},
			&armcontainerregistry.RegistriesClientBeginGenerateCredentialsOptions{ResumeToken: ""})
		if err != nil {
			return "", "", err
		}

		genCredentialsResp, err := poller.PollUntilDone(context.Background(), 2*time.Second)
		if err != nil {
			return "", "", err
		}

		for i, tokPassword := range genCredentialsResp.Passwords {
			if i == 0 {
				az.ACRPassword1 = []byte(*tokPassword.Value)
			} else if i == 1 {
				az.ACRPassword2 = []byte(*tokPassword.Value)
			}
		}

		// update the az integration
		az, err = repo.AzureIntegration().OverwriteAzureIntegration(
			az,
		)

		if err != nil {
			return "", "", err
		}
	}

	return az.ACRTokenName, string(az.ACRPassword1), nil
}

func (r *Registry) listDOCRRepositories(
	repo repository.Repository,
	doAuth *oauth2.Config,
) ([]*ptypes.RegistryRepository, error) {
	oauthInt, err := repo.OAuthIntegration().ReadOAuthIntegration(
		r.ProjectID,
		r.DOIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	tok, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, doAuth, oauth.MakeUpdateOAuthIntegrationTokenFunction(oauthInt, repo))
	if err != nil {
		return nil, err
	}

	client := godo.NewFromToken(tok)

	urlArr := strings.Split(r.URL, "/")

	if len(urlArr) != 2 {
		return nil, fmt.Errorf("invalid digital ocean registry url")
	}

	name := urlArr[1]

	repos, _, err := client.Registry.ListRepositories(context.TODO(), name, &godo.ListOptions{})
	if err != nil {
		return nil, err
	}

	res := make([]*ptypes.RegistryRepository, 0)

	for _, repo := range repos {
		res = append(res, &ptypes.RegistryRepository{
			Name: repo.Name,
			URI:  r.URL + "/" + repo.Name,
		})
	}

	return res, nil
}

func (r *Registry) listPrivateRegistryRepositories(
	repo repository.Repository,
) ([]*ptypes.RegistryRepository, error) {
	// handle dockerhub different, as it doesn't implement the docker registry http api
	if strings.Contains(r.URL, "docker.io") {
		// in this case, we just return the single dockerhub repository that's linked
		res := make([]*ptypes.RegistryRepository, 0)

		res = append(res, &ptypes.RegistryRepository{
			Name: strings.Split(r.URL, "docker.io/")[1],
			URI:  r.URL,
		})

		return res, nil
	}

	basic, err := repo.BasicIntegration().ReadBasicIntegration(
		r.ProjectID,
		r.BasicIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	// Just use service account key to authenticate, since scopes may not be in place
	// for oauth. This also prevents us from making more requests.
	client := &http.Client{}

	// get the host and scheme to make the request
	parsedURL, err := url.Parse(r.URL)

	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("%s://%s/v2/_catalog", parsedURL.Scheme, parsedURL.Host),
		nil,
	)
	if err != nil {
		return nil, err
	}

	req.SetBasicAuth(string(basic.Username), string(basic.Password))

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	// if the status code is 404, fallback to the Docker Hub implementation
	if resp.StatusCode == 404 {
		req, err := http.NewRequest(
			"GET",
			fmt.Sprintf("%s/", r.URL),
			nil,
		)
		if err != nil {
			return nil, err
		}

		req.SetBasicAuth(string(basic.Username), string(basic.Password))

		resp, err = client.Do(req)

		if err != nil {
			return nil, err
		}
	}

	gcrResp := gcrRepositoryResp{}

	if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
		return nil, fmt.Errorf("Could not read private registry repositories: %v", err)
	}

	res := make([]*ptypes.RegistryRepository, 0)

	if err != nil {
		return nil, err
	}

	for _, repo := range gcrResp.Repositories {
		res = append(res, &ptypes.RegistryRepository{
			Name: repo,
			URI:  parsedURL.Host + "/" + repo,
		})
	}

	return res, nil
}

func (r *Registry) getTokenCacheFunc(
	ctx context.Context,
	repo repository.Repository,
) ints.GetTokenCacheFunc {
	return func(ctx context.Context) (tok *ints.TokenCache, err error) {
		reg, err := repo.Registry().ReadRegistry(r.ProjectID, r.ID)
		if err != nil {
			return nil, err
		}

		return &reg.TokenCache.TokenCache, nil
	}
}

func (r *Registry) setTokenCacheFunc(
	ctx context.Context,
	repo repository.Repository,
) ints.SetTokenCacheFunc {
	return func(ctx context.Context, token string, expiry time.Time) error {
		_, err := repo.Registry().UpdateRegistryTokenCache(
			&ints.RegTokenCache{
				TokenCache: ints.TokenCache{
					Token:  []byte(token),
					Expiry: expiry,
				},
				RegistryID: r.ID,
			},
		)

		return err
	}
}

// CreateRepository creates a repository for a registry, if needed
// (currently only required for ECR)
func (r *Registry) CreateRepository(
	ctx context.Context,
	conf *config.Config,
	name string,
) error {
	ctx, span := telemetry.NewSpan(ctx, "create-repository")
	defer span.End()

	// if aws, create repository
	if r.AWSIntegrationID != 0 {
		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "aws-integration-id", Value: r.AWSIntegrationID})
		aws, err := conf.Repo.AWSIntegration().ReadAWSIntegration(
			r.ProjectID,
			r.AWSIntegrationID,
		)
		if err != nil {
			return telemetry.Error(ctx, span, err, "error reading aws integration")
		}
		err = r.createECRRepository(aws, name)
		if err != nil {
			return telemetry.Error(ctx, span, err, "error creating ecr repository")
		}
		return nil
	} else if r.GCPIntegrationID != 0 && strings.Contains(r.URL, "pkg.dev") {
		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "gcp-integration-id", Value: r.GCPIntegrationID})
		err := r.createGARRepository(ctx, conf.Repo, name)
		if err != nil {
			return telemetry.Error(ctx, span, err, "error creating gar repository")
		}
		return nil
	}

	project, err := conf.Repo.Project().ReadProject(r.ProjectID)
	if err != nil {
		return telemetry.Error(ctx, span, err, "error getting project for repository")
	}

	if project.CapiProvisionerEnabled {
		// no need to create repository if pushing to ACR
		if strings.Contains(r.URL, ".azurecr.") {
			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "skipping-create-because-azure", Value: true})
			return nil
		}

		uri := strings.TrimPrefix(r.URL, "https://")
		splits := strings.Split(uri, ".")
		accountID := splits[0]
		region := splits[3]
		req := connect.NewRequest(&porterv1.AssumeRoleCredentialsRequest{
			ProjectId:    int64(r.ProjectID),
			AwsAccountId: accountID,
		})
		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "uri", Value: uri})
		creds, err := conf.ClusterControlPlaneClient.AssumeRoleCredentials(ctx, req)
		if err != nil {
			return telemetry.Error(ctx, span, err, "error getting capi credentials for repository")
		}
		aws := &ints.AWSIntegration{
			AWSAccessKeyID:     []byte(creds.Msg.AwsAccessId),
			AWSSecretAccessKey: []byte(creds.Msg.AwsSecretKey),
			AWSSessionToken:    []byte(creds.Msg.AwsSessionToken),
			AWSRegion:          region,
		}
		err = r.createECRRepository(aws, name)
		if err != nil {
			return telemetry.Error(ctx, span, err, "error creating ecr repository")
		}
	}

	// otherwise, no-op
	return nil
}

func (r *Registry) createECRRepository(
	aws *ints.AWSIntegration,
	name string,
) error {
	sess, err := aws.GetSession()
	if err != nil {
		return err
	}

	svc := ecr.New(sess)

	// determine if repository already exists
	_, err = svc.DescribeRepositories(&ecr.DescribeRepositoriesInput{
		RepositoryNames: []*string{&name},
	})

	// if the repository was not found, create it
	if aerr, ok := err.(awserr.Error); ok && aerr.Code() == ecr.ErrCodeRepositoryNotFoundException {
		_, err = svc.CreateRepository(&ecr.CreateRepositoryInput{
			RepositoryName: &name,
		})

		return err
	} else if err != nil {
		return err
	}

	return nil
}

func (r *Registry) createGARRepository(
	ctx context.Context,
	repo repository.Repository,
	name string,
) error {
	gcpInt, err := repo.GCPIntegration().ReadGCPIntegration(
		r.ProjectID,
		r.GCPIntegrationID,
	)
	if err != nil {
		return err
	}

	client, err := artifactregistry.NewClient(ctx, option.WithTokenSource(&garTokenSource{
		reg:  r,
		repo: repo,
		ctx:  ctx,
	}), option.WithScopes("roles/artifactregistry.admin"))
	if err != nil {
		return err
	}

	defer client.Close()

	parsedURL, err := url.Parse("https://" + r.URL)
	if err != nil {
		return err
	}

	location := strings.TrimSuffix(parsedURL.Host, "-docker.pkg.dev")

	_, err = client.GetRepository(context.Background(), &artifactregistrypb.GetRepositoryRequest{
		Name: fmt.Sprintf("projects/%s/locations/%s/repositories/%s", gcpInt.GCPProjectID, location, name),
	})

	if err != nil && strings.Contains(err.Error(), "not found") {
		// create a new repository
		_, err := client.CreateRepository(context.Background(), &artifactregistrypb.CreateRepositoryRequest{
			Parent:       fmt.Sprintf("projects/%s/locations/%s", gcpInt.GCPProjectID, location),
			RepositoryId: name,
			Repository: &artifactregistrypb.Repository{
				Format: artifactregistrypb.Repository_DOCKER,
			},
		})
		if err != nil {
			return err
		}
	} else if err != nil {
		return err
	}

	return nil
}

// ListImages lists the images for an image repository
func (r *Registry) ListImages(
	ctx context.Context,
	repoName string,
	repo repository.Repository,
	conf *config.Config,
) ([]*ptypes.Image, error) {
	// switch on the auth mechanism to get a token
	if r.AWSIntegrationID != 0 {
		aws, err := repo.AWSIntegration().ReadAWSIntegration(
			r.ProjectID,
			r.AWSIntegrationID,
		)
		if err != nil {
			return nil, err
		}
		return r.listECRImages(aws, repoName, repo)
	}

	if r.AzureIntegrationID != 0 {
		return r.listACRImages(repoName, repo)
	}

	if r.GCPIntegrationID != 0 {
		if strings.Contains(r.URL, "pkg.dev") {
			return r.listGARImages(ctx, repoName, repo)
		}

		return r.listGCRImages(repoName, repo)
	}

	if r.DOIntegrationID != 0 {
		return r.listDOCRImages(repoName, repo, conf.DOConf)
	}

	if r.BasicIntegrationID != 0 {
		return r.listPrivateRegistryImages(repoName, repo)
	}

	project, err := conf.Repo.Project().ReadProject(r.ProjectID)
	if err != nil {
		return nil, fmt.Errorf("error getting project for repository: %w", err)
	}

	if project.CapiProvisionerEnabled {
		uri := strings.TrimPrefix(r.URL, "https://")
		splits := strings.Split(uri, ".")
		accountID := splits[0]
		region := splits[3]
		req := connect.NewRequest(&porterv1.AssumeRoleCredentialsRequest{
			ProjectId:    int64(r.ProjectID),
			AwsAccountId: accountID,
		})
		creds, err := conf.ClusterControlPlaneClient.AssumeRoleCredentials(ctx, req)
		if err != nil {
			return nil, fmt.Errorf("error getting capi credentials for repository: %w", err)
		}
		aws := &ints.AWSIntegration{
			AWSAccessKeyID:     []byte(creds.Msg.AwsAccessId),
			AWSSecretAccessKey: []byte(creds.Msg.AwsSecretKey),
			AWSSessionToken:    []byte(creds.Msg.AwsSessionToken),
			AWSRegion:          region,
		}
		return r.listECRImages(aws, repoName, repo)
	}

	return nil, fmt.Errorf("error listing images")
}

func (r *Registry) GetECRPaginatedImages(
	repoName string,
	repo repository.Repository,
	maxResults int64,
	nextToken *string,
) ([]*ptypes.Image, *string, error) {
	aws, err := repo.AWSIntegration().ReadAWSIntegration(
		r.ProjectID,
		r.AWSIntegrationID,
	)
	if err != nil {
		return nil, nil, err
	}

	sess, err := aws.GetSession()
	if err != nil {
		return nil, nil, err
	}

	svc := ecr.New(sess)

	resp, err := svc.ListImages(&ecr.ListImagesInput{
		RepositoryName: &repoName,
		MaxResults:     &maxResults,
		NextToken:      nextToken,
	})
	if err != nil {
		return nil, nil, err
	}

	if len(resp.ImageIds) == 0 {
		return []*ptypes.Image{}, nil, nil
	}

	imageIDLen := len(resp.ImageIds)
	imageDetails := make([]*ecr.ImageDetail, 0)
	imageIDMap := make(map[string]bool)

	for _, id := range resp.ImageIds {
		if id != nil && id.ImageTag != nil {
			imageIDMap[*id.ImageTag] = true
		}
	}

	var wg sync.WaitGroup
	var mu sync.Mutex

	// AWS API expects the length of imageIDs to be at max 100 at a time
	for start := 0; start < imageIDLen; start += 100 {
		end := start + 100
		if end > imageIDLen {
			end = imageIDLen
		}

		wg.Add(1)

		go func(start, end int) {
			defer wg.Done()

			describeResp, err := svc.DescribeImages(&ecr.DescribeImagesInput{
				RepositoryName: &repoName,
				ImageIds:       resp.ImageIds[start:end],
			})
			if err != nil {
				return
			}

			mu.Lock()
			imageDetails = append(imageDetails, describeResp.ImageDetails...)
			mu.Unlock()
		}(start, end)
	}

	wg.Wait()

	res := make([]*ptypes.Image, 0)
	imageInfoMap := make(map[string]*ptypes.Image)

	for _, img := range imageDetails {
		for _, tag := range img.ImageTags {
			newImage := &ptypes.Image{
				Digest:         *img.ImageDigest,
				Tag:            *tag,
				RepositoryName: repoName,
				PushedAt:       img.ImagePushedAt,
			}

			if _, ok := imageIDMap[*tag]; ok {
				if _, ok := imageInfoMap[*tag]; !ok {
					imageInfoMap[*tag] = newImage
				}
			}

			if len(imageInfoMap) == int(maxResults) {
				break
			}
		}

		if len(imageInfoMap) == int(maxResults) {
			break
		}
	}

	for _, v := range imageInfoMap {
		res = append(res, v)
	}

	sort.Slice(res, func(i, j int) bool {
		if res[i].PushedAt == nil || res[j].PushedAt == nil {
			return false
		}

		return res[i].PushedAt.After(*res[j].PushedAt)
	})

	return res, resp.NextToken, nil
}

func (r *Registry) listECRImages(aws *ints.AWSIntegration, repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
	sess, err := aws.GetSession()
	if err != nil {
		return nil, err
	}

	svc := ecr.New(sess)

	maxResults := int64(1000)

	var imageIDs []*ecr.ImageIdentifier

	resp, err := svc.ListImages(&ecr.ListImagesInput{
		RepositoryName: &repoName,
		MaxResults:     &maxResults,
	})
	if err != nil {
		return nil, err
	}

	if len(resp.ImageIds) == 0 {
		return []*ptypes.Image{}, nil
	}

	imageIDs = append(imageIDs, resp.ImageIds...)

	nextToken := resp.NextToken

	for nextToken != nil {
		resp, err := svc.ListImages(&ecr.ListImagesInput{
			RepositoryName: &repoName,
			MaxResults:     &maxResults,
			NextToken:      nextToken,
		})
		if err != nil {
			return nil, err
		}

		imageIDs = append(imageIDs, resp.ImageIds...)
		nextToken = resp.NextToken
	}

	imageIDLen := len(imageIDs)
	imageDetails := make([]*ecr.ImageDetail, 0)

	var wg sync.WaitGroup
	var mu sync.Mutex

	// AWS API expects the length of imageIDs to be at max 100 at a time
	for start := 0; start < imageIDLen; start += 100 {
		end := start + 100
		if end > imageIDLen {
			end = imageIDLen
		}

		wg.Add(1)

		go func(start, end int) {
			defer wg.Done()

			describeResp, err := svc.DescribeImages(&ecr.DescribeImagesInput{
				RepositoryName: &repoName,
				ImageIds:       imageIDs[start:end],
			})
			if err != nil {
				return
			}

			mu.Lock()
			imageDetails = append(imageDetails, describeResp.ImageDetails...)
			mu.Unlock()
		}(start, end)
	}

	wg.Wait()

	res := make([]*ptypes.Image, 0)
	imageInfoMap := make(map[string]*ptypes.Image)

	for _, img := range imageDetails {
		for _, tag := range img.ImageTags {
			newImage := &ptypes.Image{
				Digest:         *img.ImageDigest,
				Tag:            *tag,
				RepositoryName: repoName,
				PushedAt:       img.ImagePushedAt,
			}

			if _, ok := imageInfoMap[*tag]; !ok {
				imageInfoMap[*tag] = newImage
			}
		}
	}

	for _, v := range imageInfoMap {
		res = append(res, v)
	}

	return res, nil
}

func (r *Registry) listACRImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
	az, err := repo.AzureIntegration().ReadAzureIntegration(
		r.ProjectID,
		r.AzureIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	// use JWT token to request catalog
	client := &http.Client{}

	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("%s/v2/%s/tags/list", r.URL, repoName),
		nil,
	)
	if err != nil {
		return nil, err
	}

	req.SetBasicAuth(az.AzureClientID, string(az.ServicePrincipalSecret))

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	gcrResp := gcrImageResp{}

	if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
		return nil, fmt.Errorf("Could not read GCR repositories: %v", err)
	}

	res := make([]*ptypes.Image, 0)

	for _, tag := range gcrResp.Tags {
		res = append(res, &ptypes.Image{
			RepositoryName: strings.TrimPrefix(repoName, "https://"),
			Tag:            tag,
		})
	}

	return res, nil
}

type gcrImageResp struct {
	Tags []string `json:"tags"`
}

func (r *Registry) listGCRImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
	gcp, err := repo.GCPIntegration().ReadGCPIntegration(
		r.ProjectID,
		r.GCPIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	// use JWT token to request catalog
	client := &http.Client{}

	parsedURL, err := url.Parse("https://" + r.URL)
	if err != nil {
		return nil, err
	}

	trimmedPath := strings.Trim(parsedURL.Path, "/")

	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("https://%s/v2/%s/%s/tags/list", parsedURL.Host, trimmedPath, repoName),
		nil,
	)
	if err != nil {
		return nil, err
	}

	req.SetBasicAuth("_json_key", string(gcp.GCPKeyData))

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	gcrResp := gcrImageResp{}

	if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
		return nil, fmt.Errorf("Could not read GCR repositories: %v", err)
	}

	res := make([]*ptypes.Image, 0)

	for _, tag := range gcrResp.Tags {
		res = append(res, &ptypes.Image{
			RepositoryName: repoName,
			Tag:            tag,
		})
	}

	return res, nil
}

func (r *Registry) listGARImages(ctx context.Context, repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
	repoImageSlice := strings.Split(repoName, "/")

	if len(repoImageSlice) != 2 {
		return nil, fmt.Errorf("invalid GAR repo name: %s. Expected to be in the form of REPOSITORY/IMAGE", repoName)
	}

	gcpInt, err := repo.GCPIntegration().ReadGCPIntegration(
		r.ProjectID,
		r.GCPIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	svc, err := v1artifactregistry.NewService(ctx, option.WithTokenSource(&garTokenSource{
		reg:  r,
		repo: repo,
		ctx:  ctx,
	}), option.WithScopes("roles/artifactregistry.reader"))
	if err != nil {
		return nil, err
	}

	var res []*ptypes.Image

	parsedURL, err := url.Parse("https://" + r.URL)
	if err != nil {
		return nil, err
	}

	location := strings.TrimSuffix(parsedURL.Host, "-docker.pkg.dev")
	dockerSvc := v1artifactregistry.NewProjectsLocationsRepositoriesDockerImagesService(svc)
	nextToken := ""

	for {
		resp, err := dockerSvc.List(fmt.Sprintf("projects/%s/locations/%s/repositories/%s",
			gcpInt.GCPProjectID, location, repoImageSlice[0])).PageSize(1000).PageToken(nextToken).Do()
		if err != nil {
			return nil, err
		}

		for _, image := range resp.DockerImages {
			named, err := reference.ParseNamed(image.Uri)
			if err != nil {
				continue
			}

			paths := strings.Split(reference.Path(named), "/")

			imageName := paths[len(paths)-1]

			if imageName == repoImageSlice[1] {
				uploadTime, _ := time.Parse(time.RFC3339, image.UploadTime)

				for _, tag := range image.Tags {
					res = append(res, &ptypes.Image{
						RepositoryName: repoName,
						Tag:            tag,
						PushedAt:       &uploadTime,
						Digest:         strings.Split(image.Uri, "@")[1],
					})
				}
			}
		}

		if resp.NextPageToken == "" {
			break
		}

		nextToken = resp.NextPageToken
	}

	return res, nil
}

func (r *Registry) listDOCRImages(
	repoName string,
	repo repository.Repository,
	doAuth *oauth2.Config,
) ([]*ptypes.Image, error) {
	oauthInt, err := repo.OAuthIntegration().ReadOAuthIntegration(
		r.ProjectID,
		r.DOIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	tok, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, doAuth, oauth.MakeUpdateOAuthIntegrationTokenFunction(oauthInt, repo))
	if err != nil {
		return nil, err
	}

	client := godo.NewFromToken(tok)

	urlArr := strings.Split(r.URL, "/")

	if len(urlArr) != 2 {
		return nil, fmt.Errorf("invalid digital ocean registry url")
	}

	name := urlArr[1]

	var tags []*godo.RepositoryTag
	opt := &godo.ListOptions{
		PerPage: 200,
	}

	for {
		nextTags, resp, err := client.Registry.ListRepositoryTags(context.TODO(), name, repoName, opt)
		if err != nil {
			return nil, err
		}

		tags = append(tags, nextTags...)

		if resp.Links == nil || resp.Links.IsLastPage() {
			break
		}

		page, err := resp.Links.CurrentPage()
		if err != nil {
			return nil, err
		}

		opt.Page = page + 1
	}

	res := make([]*ptypes.Image, 0)

	for _, tag := range tags {
		res = append(res, &ptypes.Image{
			RepositoryName: repoName,
			Tag:            tag.Tag,
		})
	}

	return res, nil
}

func (r *Registry) listPrivateRegistryImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
	// handle dockerhub different, as it doesn't implement the docker registry http api
	if strings.Contains(r.URL, "docker.io") {
		return r.listDockerHubImages(repoName, repo)
	}

	basic, err := repo.BasicIntegration().ReadBasicIntegration(
		r.ProjectID,
		r.BasicIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	// Just use service account key to authenticate, since scopes may not be in place
	// for oauth. This also prevents us from making more requests.
	client := &http.Client{}

	// get the host and scheme to make the request
	parsedURL, err := url.Parse(r.URL)

	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("%s://%s/v2/%s/tags/list", parsedURL.Scheme, parsedURL.Host, repoName),
		nil,
	)
	if err != nil {
		return nil, err
	}

	req.SetBasicAuth(string(basic.Username), string(basic.Password))

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	gcrResp := gcrImageResp{}

	if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
		return nil, fmt.Errorf("Could not read private registry repositories: %v", err)
	}

	res := make([]*ptypes.Image, 0)

	for _, tag := range gcrResp.Tags {
		res = append(res, &ptypes.Image{
			RepositoryName: repoName,
			Tag:            tag,
		})
	}

	return res, nil
}

type dockerHubImageResult struct {
	Name string `json:"name"`
}

type dockerHubImageResp struct {
	Results []dockerHubImageResult `json:"results"`
}

type dockerHubLoginReq struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

type dockerHubLoginResp struct {
	Token string `json:"token"`
}

func (r *Registry) listDockerHubImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
	basic, err := repo.BasicIntegration().ReadBasicIntegration(
		r.ProjectID,
		r.BasicIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	client := &http.Client{}

	// first, make a request for the access token

	data, err := json.Marshal(&dockerHubLoginReq{
		Username: string(basic.Username),
		Password: string(basic.Password),
	})
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest(
		"POST",
		"https://hub.docker.com/v2/users/login",
		strings.NewReader(string(data)),
	)
	if err != nil {
		return nil, err
	}

	req.Header.Add("Content-Type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	tokenObj := dockerHubLoginResp{}

	if err := json.NewDecoder(resp.Body).Decode(&tokenObj); err != nil {
		return nil, fmt.Errorf("Could not decode Dockerhub token from response: %v", err)
	}

	req, err = http.NewRequest(
		"GET",
		fmt.Sprintf("https://hub.docker.com/v2/repositories/%s/tags", strings.Split(r.URL, "docker.io/")[1]),
		nil,
	)

	if err != nil {
		return nil, err
	}

	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tokenObj.Token))

	resp, err = client.Do(req)

	if err != nil {
		return nil, err
	}

	imageResp := dockerHubImageResp{}

	if err := json.NewDecoder(resp.Body).Decode(&imageResp); err != nil {
		return nil, fmt.Errorf("Could not read private registry repositories: %v", err)
	}

	res := make([]*ptypes.Image, 0)

	for _, result := range imageResp.Results {
		res = append(res, &ptypes.Image{
			RepositoryName: repoName,
			Tag:            result.Name,
		})
	}

	return res, nil
}

// GetDockerConfigJSON returns a dockerconfigjson file contents with "auths"
// populated.
func (r *Registry) GetDockerConfigJSON(
	repo repository.Repository,
	doAuth *oauth2.Config, // only required if using DOCR
) ([]byte, error) {
	var conf *configfile.ConfigFile
	var err error

	// switch on the auth mechanism to get a token
	if r.AWSIntegrationID != 0 {
		conf, err = r.getECRDockerConfigFile(repo)
	}

	if r.GCPIntegrationID != 0 {
		conf, err = r.getGCRDockerConfigFile(repo)
	}

	if r.DOIntegrationID != 0 {
		conf, err = r.getDOCRDockerConfigFile(repo, doAuth)
	}

	if r.BasicIntegrationID != 0 {
		conf, err = r.getPrivateRegistryDockerConfigFile(repo)
	}

	if r.AzureIntegrationID != 0 {
		conf, err = r.getACRDockerConfigFile(repo)
	}

	if err != nil {
		return nil, err
	}

	return json.Marshal(conf)
}

func (r *Registry) getECRDockerConfigFile(
	repo repository.Repository,
) (*configfile.ConfigFile, error) {
	aws, err := repo.AWSIntegration().ReadAWSIntegration(
		r.ProjectID,
		r.AWSIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	sess, err := aws.GetSession()
	if err != nil {
		return nil, err
	}

	ecrSvc := ecr.New(sess)

	output, err := ecrSvc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
	if err != nil {
		return nil, err
	}

	token := *output.AuthorizationData[0].AuthorizationToken

	decodedToken, err := base64.StdEncoding.DecodeString(token)
	if err != nil {
		return nil, err
	}

	parts := strings.SplitN(string(decodedToken), ":", 2)

	if len(parts) < 2 {
		return nil, err
	}

	key := r.URL

	if !strings.Contains(key, "http") {
		key = "https://" + key
	}

	return &configfile.ConfigFile{
		AuthConfigs: map[string]types.AuthConfig{
			key: {
				Username: parts[0],
				Password: parts[1],
				Auth:     token,
			},
		},
	}, nil
}

func (r *Registry) getGCRDockerConfigFile(
	repo repository.Repository,
) (*configfile.ConfigFile, error) {
	gcp, err := repo.GCPIntegration().ReadGCPIntegration(
		r.ProjectID,
		r.GCPIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	key := r.URL

	if !strings.Contains(key, "http") {
		key = "https://" + key
	}

	parsedURL, _ := url.Parse(key)

	return &configfile.ConfigFile{
		AuthConfigs: map[string]types.AuthConfig{
			parsedURL.Host: {
				Username: "_json_key",
				Password: string(gcp.GCPKeyData),
				Auth:     generateAuthToken("_json_key", string(gcp.GCPKeyData)),
			},
		},
	}, nil
}

func (r *Registry) getDOCRDockerConfigFile(
	repo repository.Repository,
	doAuth *oauth2.Config,
) (*configfile.ConfigFile, error) {
	oauthInt, err := repo.OAuthIntegration().ReadOAuthIntegration(
		r.ProjectID,
		r.DOIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	tok, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, doAuth, oauth.MakeUpdateOAuthIntegrationTokenFunction(oauthInt, repo))
	if err != nil {
		return nil, err
	}

	key := r.URL

	if !strings.Contains(key, "http") {
		key = "https://" + key
	}

	parsedURL, _ := url.Parse(key)

	return &configfile.ConfigFile{
		AuthConfigs: map[string]types.AuthConfig{
			parsedURL.Host: {
				Username: tok,
				Password: tok,
				Auth:     generateAuthToken(tok, tok),
			},
		},
	}, nil
}

func (r *Registry) getPrivateRegistryDockerConfigFile(
	repo repository.Repository,
) (*configfile.ConfigFile, error) {
	basic, err := repo.BasicIntegration().ReadBasicIntegration(
		r.ProjectID,
		r.BasicIntegrationID,
	)
	if err != nil {
		return nil, err
	}

	key := r.URL

	if !strings.Contains(key, "http") {
		key = "https://" + key
	}

	parsedURL, _ := url.Parse(key)

	authConfigKey := parsedURL.Host

	if strings.Contains(r.URL, "index.docker.io") {
		authConfigKey = "https://index.docker.io/v1/"
	}

	return &configfile.ConfigFile{
		AuthConfigs: map[string]types.AuthConfig{
			authConfigKey: {
				Username: string(basic.Username),
				Password: string(basic.Password),
				Auth:     generateAuthToken(string(basic.Username), string(basic.Password)),
			},
		},
	}, nil
}

func (r *Registry) getACRDockerConfigFile(
	repo repository.Repository,
) (*configfile.ConfigFile, error) {
	username, pw, err := r.GetACRCredentials(repo)
	if err != nil {
		return nil, err
	}

	key := r.URL

	if !strings.Contains(key, "http") {
		key = "https://" + key
	}

	parsedURL, _ := url.Parse(key)

	return &configfile.ConfigFile{
		AuthConfigs: map[string]types.AuthConfig{
			parsedURL.Host: {
				Username: string(username),
				Password: string(pw),
				Auth:     generateAuthToken(string(username), string(pw)),
			},
		},
	}, nil
}

func generateAuthToken(username, password string) string {
	return base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
}
