package image

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/docker/cli/e2e/internal/fixtures"
	"github.com/docker/cli/internal/test/environment"
	"github.com/docker/cli/internal/test/output"
	"gotest.tools/v3/assert"
	is "gotest.tools/v3/assert/cmp"
	"gotest.tools/v3/fs"
	"gotest.tools/v3/icmd"
	"gotest.tools/v3/skip"
)

func TestBuildFromContextDirectoryWithTag(t *testing.T) {
	t.Setenv("DOCKER_BUILDKIT", "0")

	dir := fs.NewDir(t, "test-build-context-dir",
		fs.WithFile("run", "echo running", fs.WithMode(0o755)),
		fs.WithDir("data", fs.WithFile("one", "1111")),
		fs.WithFile("Dockerfile", fmt.Sprintf(`
	FROM %s
	COPY run /usr/bin/run
	RUN run
	COPY data /data
		`, fixtures.AlpineImage)))
	defer dir.Remove()

	result := icmd.RunCmd(
		icmd.Command("docker", "build", "-t", "myimage", "."),
		withWorkingDir(dir))
	defer icmd.RunCommand("docker", "image", "rm", "myimage")

	const buildkitDisabledWarning = `DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
            BuildKit is currently disabled; enable it by removing the DOCKER_BUILDKIT=0
            environment-variable.
`

	result.Assert(t, icmd.Expected{Err: buildkitDisabledWarning})
	output.Assert(t, result.Stdout(), map[int]func(string) error{
		0: output.Prefix("Sending build context to Docker daemon"),
		1: output.Suffix("Step 1/4 : FROM registry:5000/alpine:frozen"),
		3: output.Suffix("Step 2/4 : COPY run /usr/bin/run"),
		5: output.Suffix("Step 3/4 : RUN run"),
		7: output.Suffix("running"),
		// TODO(krissetto): ugly, remove when no longer testing against moby 24. see https://github.com/moby/moby/pull/46270
		8: func(s string) error {
			err := output.Contains("Removed intermediate container")(s) // moby >= v25
			if err == nil {
				return nil
			}
			return output.Contains("Removing intermediate container")(s) // moby < v25
		},
		10: output.Suffix("Step 4/4 : COPY data /data"),
		12: output.Contains("Successfully built "),
		13: output.Suffix("Successfully tagged myimage:latest"),
	})
}

func TestTrustedBuild(t *testing.T) {
	skip.If(t, environment.RemoteDaemon())
	t.Setenv("DOCKER_BUILDKIT", "0")

	dir := fixtures.SetupConfigFile(t)
	defer dir.Remove()
	image1 := fixtures.CreateMaskedTrustedRemoteImage(t, registryPrefix, "trust-build1", "latest")
	image2 := fixtures.CreateMaskedTrustedRemoteImage(t, registryPrefix, "trust-build2", "latest")

	buildDir := fs.NewDir(t, "test-trusted-build-context-dir",
		fs.WithFile("Dockerfile", fmt.Sprintf(`
	FROM %s as build-base
	RUN echo ok > /foo
	FROM %s
	COPY --from=build-base foo bar
		`, image1, image2)))
	defer buildDir.Remove()

	result := icmd.RunCmd(
		icmd.Command("docker", "build", "-t", "myimage", "."),
		withWorkingDir(buildDir),
		fixtures.WithConfig(dir.Path()),
		fixtures.WithTrust,
		fixtures.WithNotary,
	)

	result.Assert(t, icmd.Expected{
		Out: fmt.Sprintf("FROM %s@sha", image1[:len(image1)-7]),
		Err: fmt.Sprintf("Tagging %s@sha", image1[:len(image1)-7]),
	})
	result.Assert(t, icmd.Expected{
		Out: fmt.Sprintf("FROM %s@sha", image2[:len(image2)-7]),
	})
}

func TestTrustedBuildUntrustedImage(t *testing.T) {
	skip.If(t, environment.RemoteDaemon())
	t.Setenv("DOCKER_BUILDKIT", "0")

	dir := fixtures.SetupConfigFile(t)
	defer dir.Remove()
	buildDir := fs.NewDir(t, "test-trusted-build-context-dir",
		fs.WithFile("Dockerfile", fmt.Sprintf(`
	FROM %s
	RUN []
		`, fixtures.AlpineImage)))
	defer buildDir.Remove()

	result := icmd.RunCmd(
		icmd.Command("docker", "build", "-t", "myimage", "."),
		withWorkingDir(buildDir),
		fixtures.WithConfig(dir.Path()),
		fixtures.WithTrust,
		fixtures.WithNotary,
	)

	result.Assert(t, icmd.Expected{
		ExitCode: 1,
		Err:      "does not have trust data for",
	})
}

func TestBuildIidFileSquash(t *testing.T) {
	environment.SkipIfNotExperimentalDaemon(t)
	t.Setenv("DOCKER_BUILDKIT", "0")

	dir := fs.NewDir(t, "test-iidfile-squash")
	defer dir.Remove()
	iidfile := filepath.Join(dir.Path(), "idsquash")
	buildDir := fs.NewDir(t, "test-iidfile-squash-build",
		fs.WithFile("Dockerfile", fmt.Sprintf(`
	FROM %s
	ENV FOO=FOO
	ENV BAR=BAR
	RUN touch /fiip
	RUN touch /foop`, fixtures.AlpineImage)),
	)
	defer buildDir.Remove()

	imageTag := "testbuildiidfilesquash"
	result := icmd.RunCmd(
		icmd.Command("docker", "build", "--iidfile", iidfile, "--squash", "-t", imageTag, "."),
		withWorkingDir(buildDir),
	)
	result.Assert(t, icmd.Success)
	id, err := os.ReadFile(iidfile)
	assert.NilError(t, err)
	result = icmd.RunCommand("docker", "image", "inspect", "-f", "{{.Id}}", imageTag)
	result.Assert(t, icmd.Success)
	assert.Check(t, is.Equal(string(id), strings.TrimSpace(result.Combined())))
}

func withWorkingDir(dir *fs.Dir) func(*icmd.Cmd) {
	return func(cmd *icmd.Cmd) {
		cmd.Dir = dir.Path()
	}
}
