package graph

import (
	"bytes"
	"io"
	"os"
	"path"
	"testing"

	"github.com/docker/docker/daemon/graphdriver"
	_ "github.com/docker/docker/daemon/graphdriver/vfs" // import the vfs driver so it is used in the tests
	"github.com/docker/docker/image"
	"github.com/docker/docker/utils"
	"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
)

const (
	testOfficialImageName    = "myapp"
	testOfficialImageID      = "1a2d3c4d4e5fa2d2a21acea242a5e2345d3aefc3e7dfa2a2a2a21a2a2ad2d234"
	testOfficialImageIDShort = "1a2d3c4d4e5f"
	testPrivateImageName     = "127.0.0.1:8000/privateapp"
	testPrivateImageID       = "5bc255f8699e4ee89ac4469266c3d11515da88fdcbde45d7b069b636ff4efd81"
	testPrivateImageIDShort  = "5bc255f8699e"
	testPrivateImageDigest   = "sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb"
	testPrivateImageTag      = "sometag"
)

func fakeTar() (io.Reader, error) {
	uid := os.Getuid()
	gid := os.Getgid()

	content := []byte("Hello world!\n")
	buf := new(bytes.Buffer)
	tw := tar.NewWriter(buf)
	for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
		hdr := new(tar.Header)

		// Leaving these fields blank requires root privileges
		hdr.Uid = uid
		hdr.Gid = gid

		hdr.Size = int64(len(content))
		hdr.Name = name
		if err := tw.WriteHeader(hdr); err != nil {
			return nil, err
		}
		tw.Write([]byte(content))
	}
	tw.Close()
	return buf, nil
}

func mkTestTagStore(root string, t *testing.T) *TagStore {
	driver, err := graphdriver.New(root, nil)
	if err != nil {
		t.Fatal(err)
	}
	graph, err := NewGraph(root, driver)
	if err != nil {
		t.Fatal(err)
	}
	store, err := NewTagStore(path.Join(root, "tags"), graph, nil)
	if err != nil {
		t.Fatal(err)
	}
	officialArchive, err := fakeTar()
	if err != nil {
		t.Fatal(err)
	}
	img := &image.Image{ID: testOfficialImageID}
	if err := graph.Register(img, officialArchive); err != nil {
		t.Fatal(err)
	}
	if err := store.Set(testOfficialImageName, "", testOfficialImageID, false); err != nil {
		t.Fatal(err)
	}
	privateArchive, err := fakeTar()
	if err != nil {
		t.Fatal(err)
	}
	img = &image.Image{ID: testPrivateImageID}
	if err := graph.Register(img, privateArchive); err != nil {
		t.Fatal(err)
	}
	if err := store.Set(testPrivateImageName, "", testPrivateImageID, false); err != nil {
		t.Fatal(err)
	}
	if err := store.SetDigest(testPrivateImageName, testPrivateImageDigest, testPrivateImageID); err != nil {
		t.Fatal(err)
	}
	return store
}

func TestLookupImage(t *testing.T) {
	tmp, err := utils.TestDirectory("")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmp)
	store := mkTestTagStore(tmp, t)
	defer store.graph.driver.Cleanup()

	officialLookups := []string{
		testOfficialImageID,
		testOfficialImageIDShort,
		testOfficialImageName + ":" + testOfficialImageID,
		testOfficialImageName + ":" + testOfficialImageIDShort,
		testOfficialImageName,
		testOfficialImageName + ":" + DEFAULTTAG,
		"docker.io/" + testOfficialImageName,
		"docker.io/" + testOfficialImageName + ":" + DEFAULTTAG,
		"index.docker.io/" + testOfficialImageName,
		"index.docker.io/" + testOfficialImageName + ":" + DEFAULTTAG,
		"library/" + testOfficialImageName,
		"library/" + testOfficialImageName + ":" + DEFAULTTAG,
		"docker.io/library/" + testOfficialImageName,
		"docker.io/library/" + testOfficialImageName + ":" + DEFAULTTAG,
		"index.docker.io/library/" + testOfficialImageName,
		"index.docker.io/library/" + testOfficialImageName + ":" + DEFAULTTAG,
	}

	privateLookups := []string{
		testPrivateImageID,
		testPrivateImageIDShort,
		testPrivateImageName + ":" + testPrivateImageID,
		testPrivateImageName + ":" + testPrivateImageIDShort,
		testPrivateImageName,
		testPrivateImageName + ":" + DEFAULTTAG,
	}

	invalidLookups := []string{
		testOfficialImageName + ":" + "fail",
		"fail:fail",
	}

	digestLookups := []string{
		testPrivateImageName + "@" + testPrivateImageDigest,
	}

	for _, name := range officialLookups {
		if img, err := store.LookupImage(name); err != nil {
			t.Errorf("Error looking up %s: %s", name, err)
		} else if img == nil {
			t.Errorf("Expected 1 image, none found: %s", name)
		} else if img.ID != testOfficialImageID {
			t.Errorf("Expected ID '%s' found '%s'", testOfficialImageID, img.ID)
		}
	}

	for _, name := range privateLookups {
		if img, err := store.LookupImage(name); err != nil {
			t.Errorf("Error looking up %s: %s", name, err)
		} else if img == nil {
			t.Errorf("Expected 1 image, none found: %s", name)
		} else if img.ID != testPrivateImageID {
			t.Errorf("Expected ID '%s' found '%s'", testPrivateImageID, img.ID)
		}
	}

	for _, name := range invalidLookups {
		if img, err := store.LookupImage(name); err == nil {
			t.Errorf("Expected error, none found: %s", name)
		} else if img != nil {
			t.Errorf("Expected 0 image, 1 found: %s", name)
		}
	}

	for _, name := range digestLookups {
		if img, err := store.LookupImage(name); err != nil {
			t.Errorf("Error looking up %s: %s", name, err)
		} else if img == nil {
			t.Errorf("Expected 1 image, none found: %s", name)
		} else if img.ID != testPrivateImageID {
			t.Errorf("Expected ID '%s' found '%s'", testPrivateImageID, img.ID)
		}
	}
}

func TestValidTagName(t *testing.T) {
	validTags := []string{"9", "foo", "foo-test", "bar.baz.boo"}
	for _, tag := range validTags {
		if err := ValidateTagName(tag); err != nil {
			t.Errorf("'%s' should've been a valid tag", tag)
		}
	}
}

func TestInvalidTagName(t *testing.T) {
	validTags := []string{"-9", ".foo", "-test", ".", "-"}
	for _, tag := range validTags {
		if err := ValidateTagName(tag); err == nil {
			t.Errorf("'%s' shouldn't have been a valid tag", tag)
		}
	}
}

func TestValidateDigest(t *testing.T) {
	tests := []struct {
		input       string
		expectError bool
	}{
		{"", true},
		{"latest", true},
		{"a:b", false},
		{"aZ0124-.+:bY852-_.+=", false},
		{"#$%#$^:$%^#$%", true},
	}

	for i, test := range tests {
		err := validateDigest(test.input)
		gotError := err != nil
		if e, a := test.expectError, gotError; e != a {
			t.Errorf("%d: with input %s, expected error=%t, got %t: %s", i, test.input, test.expectError, gotError, err)
		}
	}
}