package registries

import (
	"net/http"
	"net/url"
	"strings"

	"github.com/google/go-containerregistry/pkg/authn"
	"github.com/google/go-containerregistry/pkg/name"
	"github.com/sirupsen/logrus"
)

var _ authn.Keychain = &endpoint{}
var _ http.RoundTripper = &endpoint{}

type endpoint struct {
	auth     authn.Authenticator
	keychain authn.Keychain
	ref      name.Reference
	registry *registry
	url      *url.URL
}

// Resolve returns an authenticator for the authn.Keychain interface. The authenticator
// provides credentials to a registry by returning the credentials from mirror endpoints.
// If there were no credentials provided for this endpoint, the default keychain is used
// as a fallback, followed by simply anonymous access.
func (e endpoint) Resolve(target authn.Resource) (authn.Authenticator, error) {
	if e.auth != nil && e.auth != authn.Anonymous {
		return e.auth, nil
	}
	if e.keychain != nil {
		return e.keychain.Resolve(target)
	}
	return authn.Anonymous, nil
}

// RoundTrip handles making a request to an endpoint. It is responsible for rewriting the request
// URL to reflect the scheme, host, and path specified in the endpoint config. The transport itself
// will be retrieved from the registry config, potentially using a cached entry.
func (e endpoint) RoundTrip(req *http.Request) (*http.Response, error) {
	endpointURL := e.url
	originalURL := req.URL.String()

	// Only rewrite the URL if the request is being made against the original registry host.
	// We might have been redirected to a different URL as part of the auth
	// workflow, and must not rewrite URLs if that's the case.
	if e.ref.Context().RegistryStr() == req.URL.Host {
		if strings.HasPrefix(req.URL.Path, "/v2") {
			// The default base path is /v2; if a path is included in the endpoint,
			// replace the /v2 prefix from the request path with the endpoint path.
			// This behavior is cribbed from containerd.
			if endpointURL.Path != "" {
				req.URL.Path = endpointURL.Path + strings.TrimPrefix(req.URL.Path, "/v2")

				// If either URL has RawPath set (due to the path including urlencoded
				// characters), it also needs to be used to set the combined URL
				if endpointURL.RawPath != "" || req.URL.RawPath != "" {
					endpointPath := endpointURL.Path
					if endpointURL.RawPath != "" {
						endpointPath = endpointURL.RawPath
					}
					reqPath := req.URL.Path
					if req.URL.RawPath != "" {
						reqPath = req.URL.RawPath
					}
					req.URL.RawPath = endpointPath + strings.TrimPrefix(reqPath, "/v2")
				}
			}
		}

		// override request host and scheme
		req.Host = endpointURL.Host
		req.URL.Host = endpointURL.Host
		req.URL.Scheme = endpointURL.Scheme
	}

	if newURL := req.URL.String(); originalURL != newURL {
		logrus.Debugf("Registry endpoint URL modified: %s => %s", originalURL, newURL)
	}
	return e.registry.getTransport(req.URL).RoundTrip(req)
}
