package reference

import (
	"testing"

	"github.com/docker/distribution/digest"
)

func TestValidateReferenceName(t *testing.T) {
	validRepoNames := []string{
		"docker/docker",
		"library/debian",
		"debian",
		"docker.io/docker/docker",
		"docker.io/library/debian",
		"docker.io/debian",
		"index.docker.io/docker/docker",
		"index.docker.io/library/debian",
		"index.docker.io/debian",
		"127.0.0.1:5000/docker/docker",
		"127.0.0.1:5000/library/debian",
		"127.0.0.1:5000/debian",
		"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
	}
	invalidRepoNames := []string{
		"https://github.com/docker/docker",
		"docker/Docker",
		"-docker",
		"-docker/docker",
		"-docker.io/docker/docker",
		"docker///docker",
		"docker.io/docker/Docker",
		"docker.io/docker///docker",
		"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
		"docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
	}

	for _, name := range invalidRepoNames {
		_, err := ParseNamed(name)
		if err == nil {
			t.Fatalf("Expected invalid repo name for %q", name)
		}
	}

	for _, name := range validRepoNames {
		_, err := ParseNamed(name)
		if err != nil {
			t.Fatalf("Error parsing repo name %s, got: %q", name, err)
		}
	}
}

func TestValidateRemoteName(t *testing.T) {
	validRepositoryNames := []string{
		// Sanity check.
		"docker/docker",

		// Allow 64-character non-hexadecimal names (hexadecimal names are forbidden).
		"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",

		// Allow embedded hyphens.
		"docker-rules/docker",

		// Allow multiple hyphens as well.
		"docker---rules/docker",

		//Username doc and image name docker being tested.
		"doc/docker",

		// single character names are now allowed.
		"d/docker",
		"jess/t",

		// Consecutive underscores.
		"dock__er/docker",
	}
	for _, repositoryName := range validRepositoryNames {
		_, err := ParseNamed(repositoryName)
		if err != nil {
			t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
		}
	}

	invalidRepositoryNames := []string{
		// Disallow capital letters.
		"docker/Docker",

		// Only allow one slash.
		"docker///docker",

		// Disallow 64-character hexadecimal.
		"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",

		// Disallow leading and trailing hyphens in namespace.
		"-docker/docker",
		"docker-/docker",
		"-docker-/docker",

		// Don't allow underscores everywhere (as opposed to hyphens).
		"____/____",

		"_docker/_docker",

		// Disallow consecutive periods.
		"dock..er/docker",
		"dock_.er/docker",
		"dock-.er/docker",

		// No repository.
		"docker/",

		//namespace too long
		"this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255/docker",
	}
	for _, repositoryName := range invalidRepositoryNames {
		if _, err := ParseNamed(repositoryName); err == nil {
			t.Errorf("Repository name should be invalid: %v", repositoryName)
		}
	}
}

func TestParseRepositoryInfo(t *testing.T) {
	type tcase struct {
		RemoteName, NormalizedName, FullName, AmbiguousName, Hostname string
	}

	tcases := []tcase{
		{
			RemoteName:     "fooo/bar",
			NormalizedName: "docker.io/fooo/bar",
			FullName:       "docker.io/fooo/bar",
			AmbiguousName:  "",
			Hostname:       "docker.io",
		},
		{
			RemoteName:     "library/bar",
			NormalizedName: "docker.io/bar",
			FullName:       "docker.io/library/bar",
			AmbiguousName:  "index.docker.io/library/bar",
			Hostname:       "docker.io",
		},
		{
			RemoteName:     "fooo/bar",
			NormalizedName: "fooo/bar",
			FullName:       "fooo/bar",
			AmbiguousName:  "",
			Hostname:       "",
		},
		{
			RemoteName:     "library/ubuntu",
			NormalizedName: "library/ubuntu",
			FullName:       "library/ubuntu",
			AmbiguousName:  "",
			Hostname:       "",
		},
		{
			RemoteName:     "nonlibrary/ubuntu",
			NormalizedName: "nonlibrary/ubuntu",
			FullName:       "nonlibrary/ubuntu",
			AmbiguousName:  "",
			Hostname:       "",
		},
		{
			RemoteName:     "other/library",
			NormalizedName: "other/library",
			FullName:       "other/library",
			AmbiguousName:  "",
			Hostname:       "",
		},
		{
			RemoteName:     "private/moonbase",
			NormalizedName: "127.0.0.1:8000/private/moonbase",
			FullName:       "127.0.0.1:8000/private/moonbase",
			AmbiguousName:  "",
			Hostname:       "127.0.0.1:8000",
		},
		{
			RemoteName:     "privatebase",
			NormalizedName: "127.0.0.1:8000/privatebase",
			FullName:       "127.0.0.1:8000/privatebase",
			AmbiguousName:  "",
			Hostname:       "127.0.0.1:8000",
		},
		{
			RemoteName:     "private/moonbase",
			NormalizedName: "example.com/private/moonbase",
			FullName:       "example.com/private/moonbase",
			AmbiguousName:  "",
			Hostname:       "example.com",
		},
		{
			RemoteName:     "privatebase",
			NormalizedName: "example.com/privatebase",
			FullName:       "example.com/privatebase",
			AmbiguousName:  "",
			Hostname:       "example.com",
		},
		{
			RemoteName:     "private/moonbase",
			NormalizedName: "example.com:8000/private/moonbase",
			FullName:       "example.com:8000/private/moonbase",
			AmbiguousName:  "",
			Hostname:       "example.com:8000",
		},
		{
			RemoteName:     "privatebasee",
			NormalizedName: "example.com:8000/privatebasee",
			FullName:       "example.com:8000/privatebasee",
			AmbiguousName:  "",
			Hostname:       "example.com:8000",
		},
		{
			RemoteName:     "library/ubuntu-12.04-base",
			NormalizedName: "docker.io/ubuntu-12.04-base",
			FullName:       "docker.io/library/ubuntu-12.04-base",
			AmbiguousName:  "index.docker.io/library/ubuntu-12.04-base",
			Hostname:       "docker.io",
		},
	}

	for _, tcase := range tcases {
		refStrings := []string{tcase.NormalizedName, tcase.FullName}
		if tcase.AmbiguousName != "" {
			refStrings = append(refStrings, tcase.AmbiguousName)
		}

		var refs []Named
		for _, r := range refStrings {
			named, err := ParseNamed(r)
			if err != nil {
				t.Fatal(err)
			}
			refs = append(refs, named)
			named, err = WithName(r)
			if err != nil {
				t.Fatal(err)
			}
			refs = append(refs, named)
		}

		for _, r := range refs {
			if expected, actual := tcase.NormalizedName, r.Name(); expected != actual {
				t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual)
			}
			if expected, actual := tcase.FullName, r.FullName(); expected != actual {
				t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual)
			}
			if expected, actual := tcase.Hostname, r.Hostname(); expected != actual {
				t.Fatalf("Invalid hostname for %q. Expected %q, got %q", r, expected, actual)
			}
			if expected, actual := tcase.RemoteName, r.RemoteName(); expected != actual {
				t.Fatalf("Invalid remoteName for %q. Expected %q, got %q", r, expected, actual)
			}

		}
	}
}

func TestParseReferenceWithTagAndDigest(t *testing.T) {
	ref, err := ParseNamed("busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa")
	if err != nil {
		t.Fatal(err)
	}
	if _, isTagged := ref.(NamedTagged); isTagged {
		t.Fatalf("Reference from %q should not support tag", ref)
	}
	if _, isCanonical := ref.(Canonical); !isCanonical {
		t.Fatalf("Reference from %q should not support digest", ref)
	}
	if expected, actual := "busybox@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa", ref.String(); actual != expected {
		t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
	}
}

func TestInvalidReferenceComponents(t *testing.T) {
	if _, err := WithName("-foo"); err == nil {
		t.Fatal("Expected WithName to detect invalid name")
	}
	ref, err := WithName("busybox")
	if err != nil {
		t.Fatal(err)
	}
	if _, err := WithTag(ref, "-foo"); err == nil {
		t.Fatal("Expected WithName to detect invalid tag")
	}
	if _, err := WithDigest(ref, digest.Digest("foo")); err == nil {
		t.Fatal("Expected WithName to detect invalid digest")
	}
}
