package container

import (
	"context"
	"io"
	"io/ioutil"
	"os"
	"strings"
	"testing"

	"github.com/docker/cli/internal/test"
	"github.com/docker/cli/internal/test/testutil"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/gotestyourself/gotestyourself/fs"
	"github.com/pkg/errors"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestCIDFileNoOPWithNoFilename(t *testing.T) {
	file, err := newCIDFile("")
	require.NoError(t, err)
	assert.Equal(t, &cidFile{}, file)

	assert.NoError(t, file.Write("id"))
	assert.NoError(t, file.Close())
}

func TestNewCIDFileWhenFileAlreadyExists(t *testing.T) {
	tempfile := fs.NewFile(t, "test-cid-file")
	defer tempfile.Remove()

	_, err := newCIDFile(tempfile.Path())
	testutil.ErrorContains(t, err, "Container ID file found")
}

func TestCIDFileCloseWithNoWrite(t *testing.T) {
	tempdir := fs.NewDir(t, "test-cid-file")
	defer tempdir.Remove()

	path := tempdir.Join("cidfile")
	file, err := newCIDFile(path)
	require.NoError(t, err)
	assert.Equal(t, file.path, path)

	assert.NoError(t, file.Close())
	_, err = os.Stat(path)
	assert.True(t, os.IsNotExist(err))
}

func TestCIDFileCloseWithWrite(t *testing.T) {
	tempdir := fs.NewDir(t, "test-cid-file")
	defer tempdir.Remove()

	path := tempdir.Join("cidfile")
	file, err := newCIDFile(path)
	require.NoError(t, err)

	content := "id"
	assert.NoError(t, file.Write(content))

	actual, err := ioutil.ReadFile(path)
	require.NoError(t, err)
	assert.Equal(t, content, string(actual))

	assert.NoError(t, file.Close())
	_, err = os.Stat(path)
	require.NoError(t, err)
}

func TestCreateContainerPullsImageIfMissing(t *testing.T) {
	imageName := "does-not-exist-locally"
	responseCounter := 0
	containerID := "abcdef"

	client := &fakeClient{
		createContainerFunc: func(
			config *container.Config,
			hostConfig *container.HostConfig,
			networkingConfig *network.NetworkingConfig,
			containerName string,
		) (container.ContainerCreateCreatedBody, error) {
			defer func() { responseCounter++ }()
			switch responseCounter {
			case 0:
				return container.ContainerCreateCreatedBody{}, fakeNotFound{}
			case 1:
				return container.ContainerCreateCreatedBody{ID: containerID}, nil
			default:
				return container.ContainerCreateCreatedBody{}, errors.New("unexpected")
			}
		},
		imageCreateFunc: func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) {
			return ioutil.NopCloser(strings.NewReader("")), nil
		},
		infoFunc: func() (types.Info, error) {
			return types.Info{IndexServerAddress: "http://indexserver"}, nil
		},
	}
	cli := test.NewFakeCli(client)
	config := &containerConfig{
		Config: &container.Config{
			Image: imageName,
		},
		HostConfig: &container.HostConfig{},
	}
	body, err := createContainer(context.Background(), cli, config, "name")
	require.NoError(t, err)
	expected := container.ContainerCreateCreatedBody{ID: containerID}
	assert.Equal(t, expected, *body)
	stderr := cli.ErrBuffer().String()
	assert.Contains(t, stderr, "Unable to find image 'does-not-exist-locally:latest' locally")
}

type fakeNotFound struct{}

func (f fakeNotFound) NotFound() bool { return true }
func (f fakeNotFound) Error() string  { return "error fake not found" }
