package containerizedengine

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"testing"

	"github.com/containerd/containerd"
	"github.com/containerd/containerd/cio"
	"github.com/containerd/containerd/errdefs"
	"github.com/docker/cli/cli/streams"
	"github.com/docker/cli/internal/versions"
	clitypes "github.com/docker/cli/types"
	"github.com/docker/docker/api/types"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"gotest.tools/v3/assert"
)

func TestActivateImagePermutations(t *testing.T) {
	ctx := context.Background()
	lookedup := "not called yet"
	expectedError := fmt.Errorf("expected error")
	client := baseClient{
		cclient: &fakeContainerdClient{
			getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
				lookedup = ref
				return nil, expectedError
			},
		},
	}
	tmpdir, err := ioutil.TempDir("", "enginedir")
	assert.NilError(t, err)
	defer os.RemoveAll(tmpdir)
	metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.EnterpriseEngineImage}
	err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
	assert.NilError(t, err)

	opts := clitypes.EngineInitOptions{
		EngineVersion:      "engineversiongoeshere",
		RegistryPrefix:     "registryprefixgoeshere",
		ConfigFile:         "/tmp/configfilegoeshere",
		RuntimeMetadataDir: tmpdir,
	}

	err = client.ActivateEngine(ctx, opts, streams.NewOut(&bytes.Buffer{}), &types.AuthConfig{})
	assert.ErrorContains(t, err, expectedError.Error())
	assert.Equal(t, lookedup, fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, clitypes.EnterpriseEngineImage, opts.EngineVersion))

	metadata = clitypes.RuntimeMetadata{EngineImage: clitypes.CommunityEngineImage}
	err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
	assert.NilError(t, err)
	err = client.ActivateEngine(ctx, opts, streams.NewOut(&bytes.Buffer{}), &types.AuthConfig{})
	assert.ErrorContains(t, err, expectedError.Error())
	assert.Equal(t, lookedup, fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, clitypes.EnterpriseEngineImage, opts.EngineVersion))

	metadata = clitypes.RuntimeMetadata{EngineImage: clitypes.CommunityEngineImage + "-dm"}
	err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
	assert.NilError(t, err)
	err = client.ActivateEngine(ctx, opts, streams.NewOut(&bytes.Buffer{}), &types.AuthConfig{})
	assert.ErrorContains(t, err, expectedError.Error())
	assert.Equal(t, lookedup, fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, clitypes.EnterpriseEngineImage+"-dm", opts.EngineVersion))
}

func TestActivateConfigFailure(t *testing.T) {
	ctx := context.Background()
	registryPrefix := "registryprefixgoeshere"
	image := &fakeImage{
		nameFunc: func() string {
			return registryPrefix + "/" + clitypes.EnterpriseEngineImage + ":engineversion"
		},
		configFunc: func(ctx context.Context) (ocispec.Descriptor, error) {
			return ocispec.Descriptor{}, fmt.Errorf("config lookup failure")
		},
	}
	container := &fakeContainer{
		imageFunc: func(context.Context) (containerd.Image, error) {
			return image, nil
		},
		taskFunc: func(context.Context, cio.Attach) (containerd.Task, error) {
			return nil, errdefs.ErrNotFound
		},
		labelsFunc: func(context.Context) (map[string]string, error) {
			return map[string]string{}, nil
		},
	}
	client := baseClient{
		cclient: &fakeContainerdClient{
			containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
				return []containerd.Container{container}, nil
			},
			getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
				return image, nil
			},
		},
	}
	tmpdir, err := ioutil.TempDir("", "engindir")
	assert.NilError(t, err)
	defer os.RemoveAll(tmpdir)
	metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.CommunityEngineImage}
	err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
	assert.NilError(t, err)
	opts := clitypes.EngineInitOptions{
		EngineVersion:      "engineversiongoeshere",
		RegistryPrefix:     "registryprefixgoeshere",
		ConfigFile:         "/tmp/configfilegoeshere",
		EngineImage:        clitypes.EnterpriseEngineImage,
		RuntimeMetadataDir: tmpdir,
	}

	err = client.ActivateEngine(ctx, opts, streams.NewOut(&bytes.Buffer{}), &types.AuthConfig{})
	assert.ErrorContains(t, err, "config lookup failure")
}

func TestActivateDoUpdateFail(t *testing.T) {
	ctx := context.Background()
	registryPrefix := "registryprefixgoeshere"
	image := &fakeImage{
		nameFunc: func() string {
			return registryPrefix + "/ce-engine:engineversion"
		},
	}
	container := &fakeContainer{
		imageFunc: func(context.Context) (containerd.Image, error) {
			return image, nil
		},
	}
	client := baseClient{
		cclient: &fakeContainerdClient{
			containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
				return []containerd.Container{container}, nil
			},
			getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
				return nil, fmt.Errorf("something went wrong")

			},
		},
	}
	tmpdir, err := ioutil.TempDir("", "enginedir")
	assert.NilError(t, err)
	defer os.RemoveAll(tmpdir)
	metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.CommunityEngineImage}
	err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
	assert.NilError(t, err)
	opts := clitypes.EngineInitOptions{
		EngineVersion:      "engineversiongoeshere",
		RegistryPrefix:     "registryprefixgoeshere",
		ConfigFile:         "/tmp/configfilegoeshere",
		EngineImage:        clitypes.EnterpriseEngineImage,
		RuntimeMetadataDir: tmpdir,
	}

	err = client.ActivateEngine(ctx, opts, streams.NewOut(&bytes.Buffer{}), &types.AuthConfig{})
	assert.ErrorContains(t, err, "check for image")
	assert.ErrorContains(t, err, "something went wrong")
}

func TestDoUpdateNoVersion(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "enginedir")
	assert.NilError(t, err)
	defer os.RemoveAll(tmpdir)
	metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.EnterpriseEngineImage}
	err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
	assert.NilError(t, err)
	ctx := context.Background()
	opts := clitypes.EngineInitOptions{
		EngineVersion:      "",
		RegistryPrefix:     "registryprefixgoeshere",
		ConfigFile:         "/tmp/configfilegoeshere",
		EngineImage:        clitypes.EnterpriseEngineImage,
		RuntimeMetadataDir: tmpdir,
	}

	client := baseClient{}
	err = client.DoUpdate(ctx, opts, streams.NewOut(&bytes.Buffer{}), &types.AuthConfig{})
	assert.ErrorContains(t, err, "pick the version you")
}

func TestDoUpdateImageMiscError(t *testing.T) {
	ctx := context.Background()
	tmpdir, err := ioutil.TempDir("", "enginedir")
	assert.NilError(t, err)
	defer os.RemoveAll(tmpdir)
	metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.EnterpriseEngineImage}
	err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
	assert.NilError(t, err)
	opts := clitypes.EngineInitOptions{
		EngineVersion:      "engineversiongoeshere",
		RegistryPrefix:     "registryprefixgoeshere",
		ConfigFile:         "/tmp/configfilegoeshere",
		EngineImage:        "testnamegoeshere",
		RuntimeMetadataDir: tmpdir,
	}
	client := baseClient{
		cclient: &fakeContainerdClient{
			getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
				return nil, fmt.Errorf("something went wrong")

			},
		},
	}

	err = client.DoUpdate(ctx, opts, streams.NewOut(&bytes.Buffer{}), &types.AuthConfig{})
	assert.ErrorContains(t, err, "check for image")
	assert.ErrorContains(t, err, "something went wrong")
}

func TestDoUpdatePullFail(t *testing.T) {
	ctx := context.Background()
	tmpdir, err := ioutil.TempDir("", "enginedir")
	assert.NilError(t, err)
	defer os.RemoveAll(tmpdir)
	metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.EnterpriseEngineImage}
	err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
	assert.NilError(t, err)
	opts := clitypes.EngineInitOptions{
		EngineVersion:      "engineversiongoeshere",
		RegistryPrefix:     "registryprefixgoeshere",
		ConfigFile:         "/tmp/configfilegoeshere",
		EngineImage:        "testnamegoeshere",
		RuntimeMetadataDir: tmpdir,
	}
	client := baseClient{
		cclient: &fakeContainerdClient{
			getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
				return nil, errdefs.ErrNotFound

			},
			pullFunc: func(ctx context.Context, ref string, opts ...containerd.RemoteOpt) (containerd.Image, error) {
				return nil, fmt.Errorf("pull failure")
			},
		},
	}

	err = client.DoUpdate(ctx, opts, streams.NewOut(&bytes.Buffer{}), &types.AuthConfig{})
	assert.ErrorContains(t, err, "unable to pull")
	assert.ErrorContains(t, err, "pull failure")
}

func TestActivateDoUpdateVerifyImageName(t *testing.T) {
	ctx := context.Background()
	registryPrefix := "registryprefixgoeshere"
	image := &fakeImage{
		nameFunc: func() string {
			return registryPrefix + "/ce-engine:engineversion"
		},
	}
	container := &fakeContainer{
		imageFunc: func(context.Context) (containerd.Image, error) {
			return image, nil
		},
	}
	requestedImage := "unset"
	client := baseClient{
		cclient: &fakeContainerdClient{
			containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
				return []containerd.Container{container}, nil
			},
			getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
				requestedImage = ref
				return nil, fmt.Errorf("something went wrong")

			},
		},
	}
	tmpdir, err := ioutil.TempDir("", "enginedir")
	assert.NilError(t, err)
	defer os.RemoveAll(tmpdir)
	metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.EnterpriseEngineImage}
	err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
	assert.NilError(t, err)

	opts := clitypes.EngineInitOptions{
		EngineVersion:      "engineversiongoeshere",
		RegistryPrefix:     "registryprefixgoeshere",
		EngineImage:        "testnamegoeshere",
		ConfigFile:         "/tmp/configfilegoeshere",
		RuntimeMetadataDir: tmpdir,
	}

	err = client.ActivateEngine(ctx, opts, streams.NewOut(&bytes.Buffer{}), &types.AuthConfig{})
	assert.ErrorContains(t, err, "check for image")
	assert.ErrorContains(t, err, "something went wrong")
	expectedImage := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, opts.EngineImage, opts.EngineVersion)
	assert.Assert(t, requestedImage == expectedImage, "%s != %s", requestedImage, expectedImage)
}

func TestGetReleaseNotesURL(t *testing.T) {
	imageName := "bogus image name #$%&@!"
	url := getReleaseNotesURL(imageName)
	assert.Equal(t, url, clitypes.ReleaseNotePrefix+"/")
	imageName = "foo.bar/valid/repowithouttag"
	url = getReleaseNotesURL(imageName)
	assert.Equal(t, url, clitypes.ReleaseNotePrefix+"/")
	imageName = "foo.bar/valid/repowithouttag:tag123"
	url = getReleaseNotesURL(imageName)
	assert.Equal(t, url, clitypes.ReleaseNotePrefix+"/tag123")
}
