package v2

import (
	"net/http"
	"net/url"
	"testing"
)

type urlBuilderTestCase struct {
	description  string
	expectedPath string
	build        func() (string, error)
}

func makeURLBuilderTestCases(urlBuilder *URLBuilder) []urlBuilderTestCase {
	return []urlBuilderTestCase{
		{
			description:  "test base url",
			expectedPath: "/v2/",
			build:        urlBuilder.BuildBaseURL,
		},
		{
			description:  "test tags url",
			expectedPath: "/v2/foo/bar/tags/list",
			build: func() (string, error) {
				return urlBuilder.BuildTagsURL("foo/bar")
			},
		},
		{
			description:  "test manifest url",
			expectedPath: "/v2/foo/bar/manifests/tag",
			build: func() (string, error) {
				return urlBuilder.BuildManifestURL("foo/bar", "tag")
			},
		},
		{
			description:  "build blob url",
			expectedPath: "/v2/foo/bar/blobs/sha256:3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5",
			build: func() (string, error) {
				return urlBuilder.BuildBlobURL("foo/bar", "sha256:3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5")
			},
		},
		{
			description:  "build blob upload url",
			expectedPath: "/v2/foo/bar/blobs/uploads/",
			build: func() (string, error) {
				return urlBuilder.BuildBlobUploadURL("foo/bar")
			},
		},
		{
			description:  "build blob upload url with digest and size",
			expectedPath: "/v2/foo/bar/blobs/uploads/?digest=sha256%3A3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5&size=10000",
			build: func() (string, error) {
				return urlBuilder.BuildBlobUploadURL("foo/bar", url.Values{
					"size":   []string{"10000"},
					"digest": []string{"sha256:3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5"},
				})
			},
		},
		{
			description:  "build blob upload chunk url",
			expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part",
			build: func() (string, error) {
				return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part")
			},
		},
		{
			description:  "build blob upload chunk url with digest and size",
			expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part?digest=sha256%3A3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5&size=10000",
			build: func() (string, error) {
				return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part", url.Values{
					"size":   []string{"10000"},
					"digest": []string{"sha256:3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5"},
				})
			},
		},
	}
}

// TestURLBuilder tests the various url building functions, ensuring they are
// returning the expected values.
func TestURLBuilder(t *testing.T) {
	roots := []string{
		"http://example.com",
		"https://example.com",
		"http://localhost:5000",
		"https://localhost:5443",
	}

	for _, root := range roots {
		urlBuilder, err := NewURLBuilderFromString(root)
		if err != nil {
			t.Fatalf("unexpected error creating urlbuilder: %v", err)
		}

		for _, testCase := range makeURLBuilderTestCases(urlBuilder) {
			url, err := testCase.build()
			if err != nil {
				t.Fatalf("%s: error building url: %v", testCase.description, err)
			}

			expectedURL := root + testCase.expectedPath

			if url != expectedURL {
				t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL)
			}
		}
	}
}

func TestURLBuilderWithPrefix(t *testing.T) {
	roots := []string{
		"http://example.com/prefix/",
		"https://example.com/prefix/",
		"http://localhost:5000/prefix/",
		"https://localhost:5443/prefix/",
	}

	for _, root := range roots {
		urlBuilder, err := NewURLBuilderFromString(root)
		if err != nil {
			t.Fatalf("unexpected error creating urlbuilder: %v", err)
		}

		for _, testCase := range makeURLBuilderTestCases(urlBuilder) {
			url, err := testCase.build()
			if err != nil {
				t.Fatalf("%s: error building url: %v", testCase.description, err)
			}

			expectedURL := root[0:len(root)-1] + testCase.expectedPath

			if url != expectedURL {
				t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL)
			}
		}
	}
}

type builderFromRequestTestCase struct {
	request *http.Request
	base    string
}

func TestBuilderFromRequest(t *testing.T) {
	u, err := url.Parse("http://example.com")
	if err != nil {
		t.Fatal(err)
	}

	forwardedProtoHeader := make(http.Header, 1)
	forwardedProtoHeader.Set("X-Forwarded-Proto", "https")

	forwardedHostHeader1 := make(http.Header, 1)
	forwardedHostHeader1.Set("X-Forwarded-Host", "first.example.com")

	forwardedHostHeader2 := make(http.Header, 1)
	forwardedHostHeader2.Set("X-Forwarded-Host", "first.example.com, proxy1.example.com")

	testRequests := []struct {
		request    *http.Request
		base       string
		configHost url.URL
	}{
		{
			request: &http.Request{URL: u, Host: u.Host},
			base:    "http://example.com",
		},

		{
			request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader},
			base:    "http://example.com",
		},
		{
			request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader},
			base:    "https://example.com",
		},
		{
			request: &http.Request{URL: u, Host: u.Host, Header: forwardedHostHeader1},
			base:    "http://first.example.com",
		},
		{
			request: &http.Request{URL: u, Host: u.Host, Header: forwardedHostHeader2},
			base:    "http://first.example.com",
		},
		{
			request: &http.Request{URL: u, Host: u.Host, Header: forwardedHostHeader2},
			base:    "https://third.example.com:5000",
			configHost: url.URL{
				Scheme: "https",
				Host:   "third.example.com:5000",
			},
		},
	}

	for _, tr := range testRequests {
		var builder *URLBuilder
		if tr.configHost.Scheme != "" && tr.configHost.Host != "" {
			builder = NewURLBuilder(&tr.configHost)
		} else {
			builder = NewURLBuilderFromRequest(tr.request)
		}

		for _, testCase := range makeURLBuilderTestCases(builder) {
			buildURL, err := testCase.build()
			if err != nil {
				t.Fatalf("%s: error building url: %v", testCase.description, err)
			}

			var expectedURL string
			proto, ok := tr.request.Header["X-Forwarded-Proto"]
			if !ok {
				expectedURL = tr.base + testCase.expectedPath
			} else {
				urlBase, err := url.Parse(tr.base)
				if err != nil {
					t.Fatal(err)
				}
				urlBase.Scheme = proto[0]
				expectedURL = urlBase.String() + testCase.expectedPath
			}

			if buildURL != expectedURL {
				t.Fatalf("%s: %q != %q", testCase.description, buildURL, expectedURL)
			}
		}
	}
}

func TestBuilderFromRequestWithPrefix(t *testing.T) {
	u, err := url.Parse("http://example.com/prefix/v2/")
	if err != nil {
		t.Fatal(err)
	}

	forwardedProtoHeader := make(http.Header, 1)
	forwardedProtoHeader.Set("X-Forwarded-Proto", "https")

	testRequests := []struct {
		request    *http.Request
		base       string
		configHost url.URL
	}{
		{
			request: &http.Request{URL: u, Host: u.Host},
			base:    "http://example.com/prefix/",
		},

		{
			request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader},
			base:    "http://example.com/prefix/",
		},
		{
			request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader},
			base:    "https://example.com/prefix/",
		},
		{
			request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader},
			base:    "https://subdomain.example.com/prefix/",
			configHost: url.URL{
				Scheme: "https",
				Host:   "subdomain.example.com",
				Path:   "/prefix/",
			},
		},
	}

	for _, tr := range testRequests {
		var builder *URLBuilder
		if tr.configHost.Scheme != "" && tr.configHost.Host != "" {
			builder = NewURLBuilder(&tr.configHost)
		} else {
			builder = NewURLBuilderFromRequest(tr.request)
		}

		for _, testCase := range makeURLBuilderTestCases(builder) {
			buildURL, err := testCase.build()
			if err != nil {
				t.Fatalf("%s: error building url: %v", testCase.description, err)
			}
			var expectedURL string
			proto, ok := tr.request.Header["X-Forwarded-Proto"]
			if !ok {
				expectedURL = tr.base[0:len(tr.base)-1] + testCase.expectedPath
			} else {
				urlBase, err := url.Parse(tr.base)
				if err != nil {
					t.Fatal(err)
				}
				urlBase.Scheme = proto[0]
				expectedURL = urlBase.String()[0:len(urlBase.String())-1] + testCase.expectedPath
			}

			if buildURL != expectedURL {
				t.Fatalf("%s: %q != %q", testCase.description, buildURL, expectedURL)
			}
		}
	}
}
