package fixtures

import (
	"fmt"
	"os"
	"testing"

	"github.com/docker/cli/cli/config"
	"gotest.tools/v3/fs"
	"gotest.tools/v3/icmd"
)

const (
	// NotaryURL is the location of the notary server
	NotaryURL = "https://notary-server:4443"
	// EvilNotaryURL is the location of the evil notary server
	EvilNotaryURL = "https://evil-notary-server:4444"
	// AlpineImage is an image in the test registry
	AlpineImage = "registry:5000/alpine:frozen"
	// AlpineSha is the sha of the alpine image
	AlpineSha = "e2e16842c9b54d985bf1ef9242a313f36b856181f188de21313820e177002501" // #nosec G101 -- ignoring: Potential hardcoded credentials (gosec)
	// BusyboxImage is an image in the test registry
	BusyboxImage = "registry:5000/busybox:frozen"
	// BusyboxSha is the sha of the busybox image
	BusyboxSha = "030fcb92e1487b18c974784dcc110a93147c9fc402188370fbfd17efabffc6af" // #nosec G101 -- ignoring: Potential hardcoded credentials (gosec)
)

// SetupConfigFile creates a config.json file for testing
func SetupConfigFile(t *testing.T) fs.Dir {
	t.Helper()
	return SetupConfigWithNotaryURL(t, "trust_test", NotaryURL)
}

// SetupConfigWithNotaryURL creates a config.json file for testing in the given path
// with the given notaryURL
func SetupConfigWithNotaryURL(t *testing.T, path, notaryURL string) fs.Dir {
	t.Helper()
	dir := fs.NewDir(t, path, fs.WithMode(0o700), fs.WithFile("config.json", fmt.Sprintf(`
	{
		"auths": {
			"registry:5000": {
				"auth": "ZWlhaXM6cGFzc3dvcmQK"
			},
			"%s": {
				"auth": "ZWlhaXM6cGFzc3dvcmQK"
			}
		}
	}
	`, notaryURL)), fs.WithDir("trust", fs.WithDir("private")))
	return *dir
}

// WithConfig sets an environment variable for the docker config location
func WithConfig(dir string) func(cmd *icmd.Cmd) {
	return func(cmd *icmd.Cmd) {
		addEnvs(cmd, config.EnvOverrideConfigDir+"="+dir)
	}
}

// WithPassphrase sets environment variables for passphrases
func WithPassphrase(rootPwd, repositoryPwd string) func(cmd *icmd.Cmd) {
	return func(cmd *icmd.Cmd) {
		addEnvs(cmd,
			"DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE="+rootPwd,
			"DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE="+repositoryPwd,
		)
	}
}

// WithTrust sets DOCKER_CONTENT_TRUST to 1
func WithTrust(cmd *icmd.Cmd) {
	addEnvs(cmd, "DOCKER_CONTENT_TRUST=1")
}

// WithNotary sets the location of the notary server
func WithNotary(cmd *icmd.Cmd) {
	addEnvs(cmd, "DOCKER_CONTENT_TRUST_SERVER="+NotaryURL)
}

// WithHome sets the HOME environment variable
func WithHome(path string) func(*icmd.Cmd) {
	return func(cmd *icmd.Cmd) {
		addEnvs(cmd, "HOME="+path)
	}
}

// WithNotaryServer sets the location of the notary server
func WithNotaryServer(notaryURL string) func(*icmd.Cmd) {
	return func(cmd *icmd.Cmd) {
		addEnvs(cmd, "DOCKER_CONTENT_TRUST_SERVER="+notaryURL)
	}
}

// CreateMaskedTrustedRemoteImage creates a remote image that is signed with
// content trust, then pushes a different untrusted image at the same tag.
func CreateMaskedTrustedRemoteImage(t *testing.T, registryPrefix, repo, tag string) string {
	t.Helper()
	image := createTrustedRemoteImage(t, registryPrefix, repo, tag)
	createNamedUnsignedImageFromBusyBox(t, image)
	return image
}

func createTrustedRemoteImage(t *testing.T, registryPrefix, repo, tag string) string {
	t.Helper()
	image := fmt.Sprintf("%s/%s:%s", registryPrefix, repo, tag)
	icmd.RunCommand("docker", "image", "pull", AlpineImage).Assert(t, icmd.Success)
	icmd.RunCommand("docker", "image", "tag", AlpineImage, image).Assert(t, icmd.Success)
	result := icmd.RunCmd(
		icmd.Command("docker", "image", "push", image),
		WithPassphrase("root_password", "repo_password"), WithTrust, WithNotary)
	result.Assert(t, icmd.Success)
	icmd.RunCommand("docker", "image", "rm", image).Assert(t, icmd.Success)
	return image
}

func createNamedUnsignedImageFromBusyBox(t *testing.T, image string) {
	t.Helper()
	icmd.RunCommand("docker", "image", "pull", BusyboxImage).Assert(t, icmd.Success)
	icmd.RunCommand("docker", "image", "tag", BusyboxImage, image).Assert(t, icmd.Success)
	icmd.RunCommand("docker", "image", "push", image).Assert(t, icmd.Success)
	icmd.RunCommand("docker", "image", "rm", image).Assert(t, icmd.Success)
}

// addEnvs adds environment variables to cmd, making sure to preserve the
// current os.Environ(), which would otherwise be omitted (for non-empty .Env).
func addEnvs(cmd *icmd.Cmd, envs ...string) {
	if len(cmd.Env) == 0 {
		cmd.Env = os.Environ()
	}
	cmd.Env = append(cmd.Env, envs...)
}
