package imgsrc

import (
	"archive/tar"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"testing"

	"github.com/moby/go-archive/compression"
	"github.com/stretchr/testify/assert"
)

func newTestDir(filenames ...string) (tempDir string, err error) {
	tempDir, err = os.MkdirTemp("", "")
	if err != nil {
		return
	}

	defer func() {
		if err != nil {
			os.RemoveAll(tempDir)
		}
	}()

	for _, filename := range filenames {
		content := []byte(filename)
		filename = filepath.Join(tempDir, filename)
		err = os.MkdirAll(filepath.Dir(filename), 0o777)
		if err != nil {
			return
		}

		err = os.WriteFile(filename, content, 0o777)
		if err != nil {
			return
		}

	}

	return
}

func unpackTar(r io.ReadCloser) ([]string, map[string][]byte, error) {
	tr := tar.NewReader(r)

	names := []string{}
	out := map[string][]byte{}

	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return nil, nil, err
		}

		if hdr.Typeflag != tar.TypeReg {
			continue
		}

		names = append(names, hdr.Name)

		data, err := io.ReadAll(tr)
		if err != nil {
			return nil, nil, err
		}
		out[hdr.Name] = data
	}

	return names, out, nil
}

func TestArchiver(t *testing.T) {
	testDir, err := newTestDir("a.jpg", "content/foo.md", "images/a.jpg", "images/b.jpg")
	assert.NoError(t, err)
	defer os.RemoveAll(testDir)

	r, err := archiveDirectory(archiveOptions{
		sourcePath: testDir,
	})
	assert.NoError(t, err)

	names, _, err := unpackTar(r)
	assert.NoError(t, err)

	assert.ElementsMatch(t, names, []string{"a.jpg", "content/foo.md", "images/a.jpg", "images/b.jpg"})
}

func TestArchiverExcludes(t *testing.T) {
	testDir, err := newTestDir("a.jpg", "content/foo.md", "images/a.jpg", "images/b.jpg")
	assert.NoError(t, err)
	defer os.RemoveAll(testDir)

	r, err := archiveDirectory(archiveOptions{
		sourcePath: testDir,
		exclusions: []string{"**/*.jpg"},
	})
	assert.NoError(t, err)

	names, _, err := unpackTar(r)
	assert.NoError(t, err)

	assert.ElementsMatch(t, names, []string{"content/foo.md"})
}

func TestArchiverAdditions(t *testing.T) {
	testDir, err := newTestDir("a.jpg", "content/foo.md", "images/a.jpg", "images/b.jpg")
	assert.NoError(t, err)
	defer os.RemoveAll(testDir)

	r, err := archiveDirectory(archiveOptions{
		sourcePath: testDir,
		additions: map[string][]byte{
			"Dockerfile": []byte("this is a dockerfile"),
		},
	})
	assert.NoError(t, err)

	names, contents, err := unpackTar(r)
	assert.NoError(t, err)

	assert.Contains(t, names, "Dockerfile")
	assert.Equal(t, []byte("this is a dockerfile"), contents["Dockerfile"])
}

func TestArchiverCompression(t *testing.T) {
	testDir, err := newTestDir("a.jpg", "content/foo.md", "images/a.jpg", "images/b.jpg")
	assert.NoError(t, err)
	defer os.RemoveAll(testDir)

	r, err := archiveDirectory(archiveOptions{sourcePath: testDir, compressed: true})
	assert.NoError(t, err)
	data, err := io.ReadAll(r)
	assert.NoError(t, err)
	assert.Equal(t, compression.Gzip, compression.Detect(data))

	r, err = archiveDirectory(archiveOptions{sourcePath: testDir, compressed: false})
	assert.NoError(t, err)
	data, err = io.ReadAll(r)
	assert.NoError(t, err)
	assert.Equal(t, compression.None, compression.Detect(data))
}

func TestArchiverNoCompressionWithAdditions(t *testing.T) {
	testDir, err := newTestDir("a.jpg", "content/foo.md", "images/a.jpg", "images/b.jpg")
	assert.NoError(t, err)
	defer os.RemoveAll(testDir)

	r, err := archiveDirectory(archiveOptions{sourcePath: testDir, compressed: true, additions: map[string][]byte{
		"Dockerfile": []byte("this is a dockerfile"),
	}})
	assert.NoError(t, err)
	data, err := io.ReadAll(r)
	assert.NoError(t, err)
	assert.Equal(t, compression.None, compression.Detect(data))
}

func TestParseDockerignore(t *testing.T) {
	type testCase struct {
		input      string
		dockerfile string
		expected   []string
	}
	cases := []testCase{
		{
			input:    "node_modules\n*.jpg",
			expected: []string{"node_modules", "*.jpg"},
		},
		{
			input:    "node_modules\n*.jpg\nDockerfile",
			expected: []string{"node_modules", "*.jpg", "Dockerfile", "![Dd]ockerfile"},
		},
		{
			input:    "node_modules\n*.jpg\ndockerfile",
			expected: []string{"node_modules", "*.jpg", "dockerfile", "![Dd]ockerfile"},
		},
		{
			input:    "node_modules\n*.jpg\n.dockerignore",
			expected: []string{"node_modules", "*.jpg", ".dockerignore", "!.dockerignore"},
		},
		{
			input:      "node_modules\n*.jpg\nDockerfile\nbuild/Dockerfile",
			dockerfile: "build/Dockerfile",
			expected:   []string{"node_modules", "*.jpg", "Dockerfile", "build/Dockerfile", "!build/Dockerfile"},
		},
	}

	for _, c := range cases {
		excludes, err := parseDockerignore(strings.NewReader(c.input), c.dockerfile)
		assert.NoError(t, err)
		assert.Equal(t, c.expected, excludes, c.input)
	}
}

func TestIsPathInRoot(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}

	cases := []struct {
		filename string
		rootDir  string
		rooted   bool
	}{
		{filename: "Dockerfile", rootDir: "/a/b/c", rooted: true},
		{filename: "../Dockerfile", rootDir: "/a/b/c", rooted: false},
		{filename: "path/to/Dockerfile", rootDir: "/a/b/c", rooted: true},
		{filename: "/a/b/c/Dockerfile", rootDir: "/a/b/c", rooted: true},
		{filename: "/a/b/c/path/to/Dockerfile", rootDir: "/a/b/c", rooted: true},
		{filename: "/Dockerfile", rootDir: "/a/b/c", rooted: false},
		{filename: "/a/b/c/../Dockerfile", rootDir: "/a/b/c", rooted: false},
		{filename: "/a/b/c/path/to/../../../Dockerfile", rootDir: "/a/b/c", rooted: false},
	}

	for _, c := range cases {
		assert.Equal(t, c.rooted, isPathInRoot(c.filename, c.rootDir), "target: %s root:%s", c.filename, c.rootDir)
	}
}
