package godo

import (
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

const (
	testRegistry          = "test-registry"
	testRegion            = "r1"
	testRepository        = "test/repository"
	testEncodedRepository = "test%2Frepository"
	testTag               = "test-tag"
	testDigest            = "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
	testCompressedSize    = 2789669
	testSize              = 5843968
	testGCBlobsDeleted    = 42
	testGCFreedBytes      = 666
	testGCStatus          = "requested"
	testGCUUID            = "mew-mew-id"
	testGCType            = GCTypeUnreferencedBlobsOnly
)

var (
	testTime              = time.Date(2020, 4, 1, 0, 0, 0, 0, time.UTC)
	testTimeString        = testTime.Format(time.RFC3339)
	testGarbageCollection = &GarbageCollection{
		UUID:         testGCUUID,
		RegistryName: testRegistry,
		Status:       testGCStatus,
		CreatedAt:    testTime,
		UpdatedAt:    testTime,
		BlobsDeleted: testGCBlobsDeleted,
		FreedBytes:   testGCFreedBytes,
		Type:         testGCType,
	}
)

func TestRegistry_Create(t *testing.T) {
	setup()
	defer teardown()

	want := &Registry{
		Name:                       testRegistry,
		StorageUsageBytes:          0,
		StorageUsageBytesUpdatedAt: testTime,
		CreatedAt:                  testTime,
		Region:                     testRegion,
	}

	createRequest := &RegistryCreateRequest{
		Name:                 want.Name,
		SubscriptionTierSlug: "basic",
		Region:               testRegion,
	}

	createResponseJSON := `
{
	"registry": {
		"name": "` + testRegistry + `",
		"storage_usage_bytes": 0,
        "storage_usage_bytes_updated_at": "` + testTimeString + `",
        "created_at": "` + testTimeString + `",
		"region": "` + testRegion + `"
	},
    "subscription": {
      "tier": {
        "name": "Basic",
        "slug": "basic",
        "included_repositories": 5,
        "included_storage_bytes": 5368709120,
        "allow_storage_overage": true,
        "included_bandwidth_bytes": 5368709120,
        "monthly_price_in_cents": 500
      },
      "created_at": "` + testTimeString + `",
      "updated_at": "` + testTimeString + `"
    }
}`

	mux.HandleFunc("/v2/registry", func(w http.ResponseWriter, r *http.Request) {
		v := new(RegistryCreateRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatal(err)
		}

		testMethod(t, r, http.MethodPost)
		require.Equal(t, v, createRequest)
		fmt.Fprint(w, createResponseJSON)
	})

	got, _, err := client.Registry.Create(ctx, createRequest)
	require.NoError(t, err)
	require.Equal(t, want, got)
}

func TestRegistry_Get(t *testing.T) {
	setup()
	defer teardown()

	want := &Registry{
		Name:                       testRegistry,
		StorageUsageBytes:          0,
		StorageUsageBytesUpdatedAt: testTime,
		CreatedAt:                  testTime,
		Region:                     testRegion,
	}

	getResponseJSON := `
{
	"registry": {
		"name": "` + testRegistry + `",
		"storage_usage_bytes": 0,
        "storage_usage_bytes_updated_at": "` + testTimeString + `",
        "created_at": "` + testTimeString + `",
		"region": "` + testRegion + `"
	}
}`

	mux.HandleFunc("/v2/registry", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprint(w, getResponseJSON)
	})
	got, _, err := client.Registry.Get(ctx)
	require.NoError(t, err)
	require.Equal(t, want, got)
}

func TestRegistry_Delete(t *testing.T) {
	setup()
	defer teardown()

	mux.HandleFunc("/v2/registry", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodDelete)
	})

	_, err := client.Registry.Delete(ctx)
	require.NoError(t, err)
}

func TestRegistry_DockerCredentials(t *testing.T) {
	returnedConfig := "this could be a docker config"
	tests := []struct {
		name                  string
		params                *RegistryDockerCredentialsRequest
		expectedReadWrite     string
		expectedExpirySeconds string
	}{
		{
			name:              "read-only (default)",
			params:            &RegistryDockerCredentialsRequest{},
			expectedReadWrite: "false",
		},
		{
			name:              "read/write",
			params:            &RegistryDockerCredentialsRequest{ReadWrite: true},
			expectedReadWrite: "true",
		},
		{
			name:                  "read-only + custom expiry",
			params:                &RegistryDockerCredentialsRequest{ExpirySeconds: PtrTo(60 * 60)},
			expectedReadWrite:     "false",
			expectedExpirySeconds: "3600",
		},
		{
			name:                  "read/write + custom expiry",
			params:                &RegistryDockerCredentialsRequest{ReadWrite: true, ExpirySeconds: PtrTo(60 * 60)},
			expectedReadWrite:     "true",
			expectedExpirySeconds: "3600",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			setup()
			defer teardown()

			mux.HandleFunc("/v2/registry/docker-credentials", func(w http.ResponseWriter, r *http.Request) {
				require.Equal(t, test.expectedReadWrite, r.URL.Query().Get("read_write"))
				require.Equal(t, test.expectedExpirySeconds, r.URL.Query().Get("expiry_seconds"))
				testMethod(t, r, http.MethodGet)
				fmt.Fprint(w, returnedConfig)
			})

			got, _, err := client.Registry.DockerCredentials(ctx, test.params)
			require.NoError(t, err)
			require.Equal(t, []byte(returnedConfig), got.DockerConfigJSON)
		})
	}
}

func TestRepository_List(t *testing.T) {
	setup()
	defer teardown()

	wantRepositories := []*Repository{
		{
			RegistryName: testRegistry,
			Name:         testRepository,
			TagCount:     1,
			LatestTag: &RepositoryTag{
				RegistryName:        testRegistry,
				Repository:          testRepository,
				Tag:                 testTag,
				ManifestDigest:      testDigest,
				CompressedSizeBytes: testCompressedSize,
				SizeBytes:           testSize,
				UpdatedAt:           testTime,
			},
		},
	}
	getResponseJSON := `{
	"repositories": [
		{
			"registry_name": "` + testRegistry + `",
			"name": "` + testRepository + `",
			"tag_count": 1,
			"latest_tag": {
				"registry_name": "` + testRegistry + `",
				"repository": "` + testRepository + `",
				"tag": "` + testTag + `",
				"manifest_digest": "` + testDigest + `",
				"compressed_size_bytes": ` + fmt.Sprintf("%d", testCompressedSize) + `,
				"size_bytes": ` + fmt.Sprintf("%d", testSize) + `,
				"updated_at": "` + testTimeString + `"
			}
		}
	],
	"links": {
	    "pages": {
			"next": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/repositories?page=2",
			"last": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/repositories?page=2"
		}
	},
	"meta": {
	    "total": 2
	}
}`

	mux.HandleFunc(fmt.Sprintf("/v2/registry/%s/repositories", testRegistry), func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		testFormValues(t, r, map[string]string{"page": "1", "per_page": "1"})
		fmt.Fprint(w, getResponseJSON)
	})
	got, response, err := client.Registry.ListRepositories(ctx, testRegistry, &ListOptions{Page: 1, PerPage: 1})
	require.NoError(t, err)
	require.Equal(t, wantRepositories, got)

	gotRespLinks := response.Links
	wantRespLinks := &Links{
		Pages: &Pages{
			Next: fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositories?page=2", testRegistry),
			Last: fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositories?page=2", testRegistry),
		},
	}
	assert.Equal(t, wantRespLinks, gotRespLinks)

	gotRespMeta := response.Meta
	wantRespMeta := &Meta{
		Total: 2,
	}
	assert.Equal(t, wantRespMeta, gotRespMeta)
}

func TestRepository_ListV2(t *testing.T) {
	setup()
	defer teardown()

	wantRepositories := []*RepositoryV2{
		{
			RegistryName:  testRegistry,
			Name:          testRepository,
			TagCount:      2,
			ManifestCount: 1,
			LatestManifest: &RepositoryManifest{
				Digest:              "sha256:abc",
				RegistryName:        testRegistry,
				Repository:          testRepository,
				CompressedSizeBytes: testCompressedSize,
				SizeBytes:           testSize,
				UpdatedAt:           testTime,
				Tags:                []string{"v1", "v2"},
				Blobs: []*Blob{
					{
						Digest:              "sha256:blob1",
						CompressedSizeBytes: 100,
					},
					{
						Digest:              "sha256:blob2",
						CompressedSizeBytes: 200,
					},
				},
			},
		},
	}
	baseLinkPage := fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositoriesV2", testRegistry)
	getResponseJSON := `{
	"repositories": [
		{
			"registry_name": "` + testRegistry + `",
			"name": "` + testRepository + `",
			"tag_count": 2,
			"manifest_count": 1,
			"latest_manifest": {
				"digest": "sha256:abc",
				"registry_name": "` + testRegistry + `",
				"repository": "` + testRepository + `",
				"compressed_size_bytes": ` + fmt.Sprintf("%d", testCompressedSize) + `,
				"size_bytes": ` + fmt.Sprintf("%d", testSize) + `,
				"updated_at": "` + testTimeString + `",
				"tags": [
					"v1",
					"v2"
				],
				"blobs": [
					{
						"digest": "sha256:blob1",
						"compressed_size_bytes": 100
					},
					{
						"digest": "sha256:blob2",
						"compressed_size_bytes": 200
					}
				]
			}
		}
	],
	"links": {
	    "pages": {
			"first":    "` + baseLinkPage + `?page=1&page_size=1",
			"prev":     "` + baseLinkPage + `?page=2&page_size=1&page_token=aaa",
			"next":     "` + baseLinkPage + `?page=4&page_size=1&page_token=ccc",
			"last":     "` + baseLinkPage + `?page=5&page_size=1"
		}
	},
	"meta": {
	    "total": 5
	}
}`

	mux.HandleFunc(fmt.Sprintf("/v2/registry/%s/repositoriesV2", testRegistry), func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		testFormValues(t, r, map[string]string{"page": "3", "per_page": "1", "page_token": "bbb"})
		fmt.Fprint(w, getResponseJSON)
	})
	got, response, err := client.Registry.ListRepositoriesV2(ctx, testRegistry, &TokenListOptions{Page: 3, PerPage: 1, Token: "bbb"})
	require.NoError(t, err)
	require.Equal(t, wantRepositories, got)

	gotRespLinks := response.Links
	wantRespLinks := &Links{
		Pages: &Pages{
			First: fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositoriesV2?page=1&page_size=1", testRegistry),
			Prev:  fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositoriesV2?page=2&page_size=1&page_token=aaa", testRegistry),
			Next:  fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositoriesV2?page=4&page_size=1&page_token=ccc", testRegistry),
			Last:  fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositoriesV2?page=5&page_size=1", testRegistry),
		},
	}
	assert.Equal(t, wantRespLinks, gotRespLinks)

	gotRespMeta := response.Meta
	wantRespMeta := &Meta{
		Total: 5,
	}
	assert.Equal(t, wantRespMeta, gotRespMeta)
}

func TestRepository_ListTags(t *testing.T) {
	setup()
	defer teardown()

	wantTags := []*RepositoryTag{
		{
			RegistryName:        testRegistry,
			Repository:          testRepository,
			Tag:                 testTag,
			ManifestDigest:      testDigest,
			CompressedSizeBytes: testCompressedSize,
			SizeBytes:           testSize,
			UpdatedAt:           testTime,
		},
	}
	getResponseJSON := `{
	"tags": [
		{
			"registry_name": "` + testRegistry + `",
			"repository": "` + testRepository + `",
			"tag": "` + testTag + `",
			"manifest_digest": "` + testDigest + `",
			"compressed_size_bytes": ` + fmt.Sprintf("%d", testCompressedSize) + `,
			"size_bytes": ` + fmt.Sprintf("%d", testSize) + `,
			"updated_at": "` + testTimeString + `"
		}
	],
	"links": {
	    "pages": {
			"next": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/repositories/` + testEncodedRepository + `/tags?page=2",
			"last": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/repositories/` + testEncodedRepository + `/tags?page=2"
		}
	},
	"meta": {
	    "total": 2
	}
}`

	mux.HandleFunc(fmt.Sprintf("/v2/registry/%s/repositories/%s/tags", testRegistry, testEncodedRepository), func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		testFormValues(t, r, map[string]string{"page": "1", "per_page": "1"})
		fmt.Fprint(w, getResponseJSON)
	})
	got, response, err := client.Registry.ListRepositoryTags(ctx, testRegistry, testRepository, &ListOptions{Page: 1, PerPage: 1})
	require.NoError(t, err)
	require.Equal(t, wantTags, got)

	gotRespLinks := response.Links
	wantRespLinks := &Links{
		Pages: &Pages{
			Next: fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositories/%s/tags?page=2", testRegistry, testEncodedRepository),
			Last: fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositories/%s/tags?page=2", testRegistry, testEncodedRepository),
		},
	}
	assert.Equal(t, wantRespLinks, gotRespLinks)

	gotRespMeta := response.Meta
	wantRespMeta := &Meta{
		Total: 2,
	}
	assert.Equal(t, wantRespMeta, gotRespMeta)
}

func TestRegistry_DeleteTag(t *testing.T) {
	setup()
	defer teardown()

	mux.HandleFunc(fmt.Sprintf("/v2/registry/%s/repositories/%s/tags/%s", testRegistry, testEncodedRepository, testTag), func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodDelete)
	})

	_, err := client.Registry.DeleteTag(ctx, testRegistry, testRepository, testTag)
	require.NoError(t, err)
}

func TestRegistry_ListManifests(t *testing.T) {
	setup()
	defer teardown()

	wantTags := []*RepositoryManifest{
		{
			RegistryName:        testRegistry,
			Repository:          testRepository,
			Digest:              testDigest,
			CompressedSizeBytes: testCompressedSize,
			SizeBytes:           testSize,
			UpdatedAt:           testTime,
			Tags:                []string{"latest", "v1", "v2"},
			Blobs: []*Blob{
				{
					Digest:              "sha256:blob1",
					CompressedSizeBytes: 998,
				},
				{
					Digest:              "sha256:blob2",
					CompressedSizeBytes: 1,
				},
			},
		},
	}
	getResponseJSON := `{
	"manifests": [
		{
			"registry_name": "` + testRegistry + `",
			"repository": "` + testRepository + `",
			"digest": "` + testDigest + `",
			"compressed_size_bytes": ` + fmt.Sprintf("%d", testCompressedSize) + `,
			"size_bytes": ` + fmt.Sprintf("%d", testSize) + `,
			"updated_at": "` + testTimeString + `",
			"tags": [ "latest", "v1", "v2" ],
			"blobs": [
				{
					"digest": "sha256:blob1",
					"compressed_size_bytes": 998
				},
				{

					"digest": "sha256:blob2",
					"compressed_size_bytes": 1
				}
			]
		}
	],
	"links": {
	    "pages": {
			"first": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/repositories/` + testEncodedRepository + `/digests?page=1&page_size=1",
			"prev": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/repositories/` + testEncodedRepository + `/digests?page=2&page_size=1",
			"next": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/repositories/` + testEncodedRepository + `/digests?page=4&page_size=1",
			"last": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/repositories/` + testEncodedRepository + `/digests?page=5&page_size=1"
		}
	},
	"meta": {
	    "total": 5
	}
}`

	mux.HandleFunc(fmt.Sprintf("/v2/registry/%s/repositories/%s/digests", testRegistry, testEncodedRepository), func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		testFormValues(t, r, map[string]string{"page": "3", "per_page": "1"})
		fmt.Fprint(w, getResponseJSON)
	})
	got, response, err := client.Registry.ListRepositoryManifests(ctx, testRegistry, testRepository, &ListOptions{Page: 3, PerPage: 1})
	require.NoError(t, err)
	require.Equal(t, wantTags, got)

	gotRespLinks := response.Links
	wantRespLinks := &Links{
		Pages: &Pages{
			First: fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositories/%s/digests?page=1&page_size=1", testRegistry, testEncodedRepository),
			Prev:  fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositories/%s/digests?page=2&page_size=1", testRegistry, testEncodedRepository),
			Next:  fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositories/%s/digests?page=4&page_size=1", testRegistry, testEncodedRepository),
			Last:  fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositories/%s/digests?page=5&page_size=1", testRegistry, testEncodedRepository),
		},
	}
	assert.Equal(t, wantRespLinks, gotRespLinks)

	gotRespMeta := response.Meta
	wantRespMeta := &Meta{
		Total: 5,
	}
	assert.Equal(t, wantRespMeta, gotRespMeta)
}

func TestRegistry_DeleteManifest(t *testing.T) {
	setup()
	defer teardown()

	mux.HandleFunc(fmt.Sprintf("/v2/registry/%s/repositories/%s/digests/%s", testRegistry, testEncodedRepository, testDigest), func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodDelete)
	})

	_, err := client.Registry.DeleteManifest(ctx, testRegistry, testRepository, testDigest)
	require.NoError(t, err)
}

func reifyTemplateStr(t *testing.T, tmplStr string, v interface{}) string {
	tmpl, err := template.New("meow").Parse(tmplStr)
	require.NoError(t, err)

	s := &strings.Builder{}
	err = tmpl.Execute(s, v)
	require.NoError(t, err)

	return s.String()
}

func TestGarbageCollection_Start(t *testing.T) {
	setup()
	defer teardown()

	want := testGarbageCollection
	requestResponseJSONTmpl := `
{
  "garbage_collection": {
    "uuid": "{{.UUID}}",
    "registry_name": "{{.RegistryName}}",
    "status": "{{.Status}}",
    "type": "{{.Type}}",
    "created_at": "{{.CreatedAt.Format "2006-01-02T15:04:05Z07:00"}}",
    "updated_at": "{{.UpdatedAt.Format "2006-01-02T15:04:05Z07:00"}}",
    "blobs_deleted": {{.BlobsDeleted}},
    "freed_bytes": {{.FreedBytes}}
  }
}`
	requestResponseJSON := reifyTemplateStr(t, requestResponseJSONTmpl, want)

	createRequest := &StartGarbageCollectionRequest{
		Type: GCTypeUnreferencedBlobsOnly,
	}
	mux.HandleFunc("/v2/registry/"+testRegistry+"/garbage-collection",
		func(w http.ResponseWriter, r *http.Request) {
			v := new(StartGarbageCollectionRequest)
			err := json.NewDecoder(r.Body).Decode(v)
			if err != nil {
				t.Fatal(err)
			}

			testMethod(t, r, http.MethodPost)
			require.Equal(t, v, createRequest)
			fmt.Fprint(w, requestResponseJSON)
		})

	got, _, err := client.Registry.StartGarbageCollection(ctx, testRegistry)
	require.NoError(t, err)
	require.Equal(t, want, got)
}

func TestGarbageCollection_Get(t *testing.T) {
	setup()
	defer teardown()

	want := testGarbageCollection
	requestResponseJSONTmpl := `
{
  "garbage_collection": {
    "uuid": "{{.UUID}}",
    "registry_name": "{{.RegistryName}}",
    "status": "{{.Status}}",
    "type": "{{.Type}}",
    "created_at": "{{.CreatedAt.Format "2006-01-02T15:04:05Z07:00"}}",
    "updated_at": "{{.UpdatedAt.Format "2006-01-02T15:04:05Z07:00"}}",
    "blobs_deleted": {{.BlobsDeleted}},
    "freed_bytes": {{.FreedBytes}}
  }
}`
	requestResponseJSON := reifyTemplateStr(t, requestResponseJSONTmpl, want)

	mux.HandleFunc("/v2/registry/"+testRegistry+"/garbage-collection",
		func(w http.ResponseWriter, r *http.Request) {
			testMethod(t, r, http.MethodGet)
			fmt.Fprint(w, requestResponseJSON)
		})

	got, _, err := client.Registry.GetGarbageCollection(ctx, testRegistry)
	require.NoError(t, err)
	require.Equal(t, want, got)
}

func TestGarbageCollection_List(t *testing.T) {
	setup()
	defer teardown()

	want := []*GarbageCollection{testGarbageCollection}
	requestResponseJSONTmpl := `
{
  "garbage_collections": [
    {
      "uuid": "{{.UUID}}",
      "registry_name": "{{.RegistryName}}",
      "status": "{{.Status}}",
      "type": "{{.Type}}",
      "created_at": "{{.CreatedAt.Format "2006-01-02T15:04:05Z07:00"}}",
      "updated_at": "{{.UpdatedAt.Format "2006-01-02T15:04:05Z07:00"}}",
      "blobs_deleted": {{.BlobsDeleted}},
      "freed_bytes": {{.FreedBytes}}
    }
  ],
	"links": {
	    "pages": {
			"next": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/garbage-collections?page=2",
			"last": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/garbage-collections?page=2"
		}
	},
	"meta": {
	    "total": 2
	}
}`
	requestResponseJSON := reifyTemplateStr(t, requestResponseJSONTmpl, testGarbageCollection)

	mux.HandleFunc("/v2/registry/"+testRegistry+"/garbage-collections",
		func(w http.ResponseWriter, r *http.Request) {
			testMethod(t, r, http.MethodGet)
			testFormValues(t, r, map[string]string{"page": "1", "per_page": "1"})
			fmt.Fprint(w, requestResponseJSON)
		})

	got, resp, err := client.Registry.ListGarbageCollections(ctx, testRegistry, &ListOptions{Page: 1, PerPage: 1})
	require.NoError(t, err)
	require.Equal(t, want, got)

	gotRespLinks := resp.Links
	wantRespLinks := &Links{
		Pages: &Pages{
			Next: fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/garbage-collections?page=2", testRegistry),
			Last: fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/garbage-collections?page=2", testRegistry),
		},
	}
	assert.Equal(t, wantRespLinks, gotRespLinks)

	gotRespMeta := resp.Meta
	wantRespMeta := &Meta{
		Total: 2,
	}
	assert.Equal(t, wantRespMeta, gotRespMeta)
}

func TestGarbageCollection_Update(t *testing.T) {
	setup()
	defer teardown()

	updateRequest := &UpdateGarbageCollectionRequest{
		Cancel: true,
	}

	want := testGarbageCollection
	requestResponseJSONTmpl := `
{
  "garbage_collection": {
    "uuid": "{{.UUID}}",
    "registry_name": "{{.RegistryName}}",
    "status": "{{.Status}}",
    "type": "{{.Type}}",
    "created_at": "{{.CreatedAt.Format "2006-01-02T15:04:05Z07:00"}}",
    "updated_at": "{{.UpdatedAt.Format "2006-01-02T15:04:05Z07:00"}}",
    "blobs_deleted": {{.BlobsDeleted}},
    "freed_bytes": {{.FreedBytes}}
  }
}`
	requestResponseJSON := reifyTemplateStr(t, requestResponseJSONTmpl, want)

	mux.HandleFunc("/v2/registry/"+testRegistry+"/garbage-collection/"+testGCUUID,
		func(w http.ResponseWriter, r *http.Request) {
			v := new(UpdateGarbageCollectionRequest)
			err := json.NewDecoder(r.Body).Decode(v)
			if err != nil {
				t.Fatal(err)
			}

			testMethod(t, r, http.MethodPut)
			require.Equal(t, v, updateRequest)
			fmt.Fprint(w, requestResponseJSON)
		})

	got, _, err := client.Registry.UpdateGarbageCollection(ctx, testRegistry, testGCUUID, updateRequest)
	require.NoError(t, err)
	require.Equal(t, want, got)
}

func TestRegistry_GetOptions(t *testing.T) {
	responseJSON := `
{
  "options": {
	"available_regions": [
		"r1",
		"r2"
	],
    "subscription_tiers": [
      {
        "name": "Starter",
        "slug": "starter",
        "included_repositories": 1,
        "included_storage_bytes": 524288000,
        "allow_storage_overage": false,
        "included_bandwidth_bytes": 524288000,
        "monthly_price_in_cents": 0,
        "eligible": false,
        "eligibility_reasons": [
          "OverStorageLimit",
          "OverRepositoryLimit"
        ]
      },
      {
        "name": "Basic",
        "slug": "basic",
        "included_repositories": 5,
        "included_storage_bytes": 5368709120,
        "allow_storage_overage": true,
        "included_bandwidth_bytes": 5368709120,
        "monthly_price_in_cents": 500,
        "eligible": false,
        "eligibility_reasons": [
          "OverRepositoryLimit"
        ]
      },
      {
        "name": "Professional",
        "slug": "professional",
        "included_repositories": 0,
        "included_storage_bytes": 107374182400,
        "allow_storage_overage": true,
        "included_bandwidth_bytes": 107374182400,
        "monthly_price_in_cents": 2000,
        "eligible": true
      }
    ]
  }
}`
	want := &RegistryOptions{
		AvailableRegions: []string{
			"r1",
			"r2",
		},
		SubscriptionTiers: []*RegistrySubscriptionTier{
			{
				Name:                   "Starter",
				Slug:                   "starter",
				IncludedRepositories:   1,
				IncludedStorageBytes:   524288000,
				AllowStorageOverage:    false,
				IncludedBandwidthBytes: 524288000,
				MonthlyPriceInCents:    0,
				Eligible:               false,
				EligibilityReasons: []string{
					"OverStorageLimit",
					"OverRepositoryLimit",
				},
			},
			{
				Name:                   "Basic",
				Slug:                   "basic",
				IncludedRepositories:   5,
				IncludedStorageBytes:   5368709120,
				AllowStorageOverage:    true,
				IncludedBandwidthBytes: 5368709120,
				MonthlyPriceInCents:    500,
				Eligible:               false,
				EligibilityReasons: []string{
					"OverRepositoryLimit",
				},
			},
			{
				Name:                   "Professional",
				Slug:                   "professional",
				IncludedRepositories:   0,
				IncludedStorageBytes:   107374182400,
				AllowStorageOverage:    true,
				IncludedBandwidthBytes: 107374182400,
				MonthlyPriceInCents:    2000,
				Eligible:               true,
			},
		},
	}

	setup()
	defer teardown()

	mux.HandleFunc("/v2/registry/options", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprint(w, responseJSON)
	})

	got, _, err := client.Registry.GetOptions(ctx)
	require.NoError(t, err)
	require.Equal(t, want, got)
}

func TestRegistry_GetSubscription(t *testing.T) {
	setup()
	defer teardown()

	want := &RegistrySubscription{
		Tier: &RegistrySubscriptionTier{
			Name:                   "Basic",
			Slug:                   "basic",
			IncludedRepositories:   5,
			IncludedStorageBytes:   5368709120,
			AllowStorageOverage:    true,
			IncludedBandwidthBytes: 5368709120,
			MonthlyPriceInCents:    500,
		},
		CreatedAt: testTime,
		UpdatedAt: testTime,
	}

	getResponseJSON := `
{
  "subscription": {
    "tier": {
      "name": "Basic",
      "slug": "basic",
      "included_repositories": 5,
      "included_storage_bytes": 5368709120,
      "allow_storage_overage": true,
      "included_bandwidth_bytes": 5368709120,
      "monthly_price_in_cents": 500
    },
    "created_at": "` + testTimeString + `",
    "updated_at": "` + testTimeString + `"
  }
}
`

	mux.HandleFunc("/v2/registry/subscription", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprint(w, getResponseJSON)
	})
	got, _, err := client.Registry.GetSubscription(ctx)
	require.NoError(t, err)
	require.Equal(t, want, got)
}

func TestRegistry_UpdateSubscription(t *testing.T) {
	setup()
	defer teardown()

	updateRequest := &RegistrySubscriptionUpdateRequest{
		TierSlug: "professional",
	}

	want := &RegistrySubscription{
		Tier: &RegistrySubscriptionTier{
			Name:                   "Professional",
			Slug:                   "professional",
			IncludedRepositories:   0,
			IncludedStorageBytes:   107374182400,
			AllowStorageOverage:    true,
			IncludedBandwidthBytes: 107374182400,
			MonthlyPriceInCents:    2000,
			Eligible:               true,
		},
		CreatedAt: testTime,
		UpdatedAt: testTime,
	}

	updateResponseJSON := `{
  "subscription": {
    "tier": {
        "name": "Professional",
        "slug": "professional",
        "included_repositories": 0,
        "included_storage_bytes": 107374182400,
        "allow_storage_overage": true,
        "included_bandwidth_bytes": 107374182400,
        "monthly_price_in_cents": 2000,
        "eligible": true
      },
    "created_at": "` + testTimeString + `",
    "updated_at": "` + testTimeString + `"
  }
}`

	mux.HandleFunc("/v2/registry/subscription",
		func(w http.ResponseWriter, r *http.Request) {
			v := new(RegistrySubscriptionUpdateRequest)
			err := json.NewDecoder(r.Body).Decode(v)
			if err != nil {
				t.Fatal(err)
			}

			testMethod(t, r, http.MethodPost)
			require.Equal(t, v, updateRequest)
			fmt.Fprint(w, updateResponseJSON)
		})

	got, _, err := client.Registry.UpdateSubscription(ctx, updateRequest)
	require.NoError(t, err)
	require.Equal(t, want, got)
}

func TestRegistry_ValidateName(t *testing.T) {
	setup()
	defer teardown()

	validateNameRequest := &RegistryValidateNameRequest{
		Name: testRegistry,
	}

	mux.HandleFunc("/v2/registry/validate-name", func(w http.ResponseWriter, r *http.Request) {
		v := new(RegistryValidateNameRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatal(err)
		}

		testMethod(t, r, http.MethodPost)
		require.Equal(t, v, validateNameRequest)
	})

	_, err := client.Registry.ValidateName(ctx, validateNameRequest)
	require.NoError(t, err)
}

// Tests for Registries service methods
func TestRegistries_Get(t *testing.T) {
	setup()
	defer teardown()

	want := &Registry{
		Name:                       testRegistry,
		StorageUsageBytes:          0,
		StorageUsageBytesUpdatedAt: testTime,
		CreatedAt:                  testTime,
		Region:                     testRegion,
	}

	// We return `read_only` and `type` (only for multi-regsitry) -- check if we need to do this or not -- older tests don't add `read_only` to the response
	getResponseJSON := `
{
	"registry": {
		"name": "` + testRegistry + `",
		"storage_usage_bytes": 0,
		"storage_usage_bytes_updated_at": "` + testTimeString + `",
		"created_at": "` + testTimeString + `",
		"region": "` + testRegion + `"
	}
}`

	mux.HandleFunc(fmt.Sprintf("/v2/registries/%s", testRegistry), func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprint(w, getResponseJSON)
	})
	got, _, err := client.Registries.Get(ctx, testRegistry)
	require.NoError(t, err)
	require.Equal(t, want, got)
}

func TestRegistries_List(t *testing.T) {
	setup()
	defer teardown()

	wantRegistries := []*Registry{
		{
			Name:                       testRegistry,
			StorageUsageBytes:          0,
			StorageUsageBytesUpdatedAt: testTime,
			CreatedAt:                  testTime,
			Region:                     testRegion,
		},
	}
	getResponseJSON := `
{
	"registries": [
		{
			"name": "` + testRegistry + `",
			"storage_usage_bytes": 0,
			"storage_usage_bytes_updated_at": "` + testTimeString + `",
			"created_at": "` + testTimeString + `",
			"region": "` + testRegion + `"
		}
	]
}`

	mux.HandleFunc("/v2/registries", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Printf("Returning: %v", getResponseJSON)
		fmt.Fprint(w, getResponseJSON)
	})
	got, _, err := client.Registries.List(ctx)
	require.NoError(t, err)
	fmt.Printf("Expected: %+v\n", wantRegistries)
	fmt.Printf("Got: %+v\n", got)
	require.Equal(t, wantRegistries, got)
}

func TestRegistries_Create(t *testing.T) {
	setup()
	defer teardown()

	want := &Registry{
		Name:                       testRegistry,
		StorageUsageBytes:          0,
		StorageUsageBytesUpdatedAt: testTime,
		CreatedAt:                  testTime,
		Region:                     testRegion,
	}

	createRequest := &RegistriesCreateRequest{
		Name:   want.Name,
		Region: testRegion,
	}

	createResponseJSON := `
{
	"registry": {
		"name": "` + testRegistry + `",
		"storage_usage_bytes": 0,
		"storage_usage_bytes_updated_at": "` + testTimeString + `",
		"created_at": "` + testTimeString + `",
		"region": "` + testRegion + `"
	}
}`

	mux.HandleFunc("/v2/registries", func(w http.ResponseWriter, r *http.Request) {
		v := new(RegistriesCreateRequest)
		err := json.NewDecoder(r.Body).Decode(v)
		if err != nil {
			t.Fatal(err)
		}

		testMethod(t, r, http.MethodPost)
		require.Equal(t, v, createRequest)
		fmt.Fprint(w, createResponseJSON)
	})

	got, _, err := client.Registries.Create(ctx, createRequest)
	require.NoError(t, err)
	require.Equal(t, want, got)
}

func TestRegistries_Delete(t *testing.T) {
	setup()
	defer teardown()

	mux.HandleFunc(fmt.Sprintf("/v2/registries/%s", testRegistry), func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodDelete)
	})

	_, err := client.Registries.Delete(ctx, testRegistry)
	require.NoError(t, err)
}

func TestRegistries_DockerCredentials(t *testing.T) {
	returnedConfig := "this could be a docker config"
	tests := []struct {
		name                  string
		params                *RegistryDockerCredentialsRequest
		expectedReadWrite     string
		expectedExpirySeconds string
	}{
		{
			name:              "read-only (default)",
			params:            &RegistryDockerCredentialsRequest{},
			expectedReadWrite: "false",
		},
		{
			name:              "read/write",
			params:            &RegistryDockerCredentialsRequest{ReadWrite: true},
			expectedReadWrite: "true",
		},
		{
			name:                  "read-only + custom expiry",
			params:                &RegistryDockerCredentialsRequest{ExpirySeconds: PtrTo(60 * 60)},
			expectedReadWrite:     "false",
			expectedExpirySeconds: "3600",
		},
		{
			name:                  "read/write + custom expiry",
			params:                &RegistryDockerCredentialsRequest{ReadWrite: true, ExpirySeconds: PtrTo(60 * 60)},
			expectedReadWrite:     "true",
			expectedExpirySeconds: "3600",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			setup()
			defer teardown()

			mux.HandleFunc(fmt.Sprintf("/v2/registries/%s/docker-credentials", testRegistry), func(w http.ResponseWriter, r *http.Request) {
				require.Equal(t, test.expectedReadWrite, r.URL.Query().Get("read_write"))
				require.Equal(t, test.expectedExpirySeconds, r.URL.Query().Get("expiry_seconds"))
				testMethod(t, r, http.MethodGet)
				fmt.Fprint(w, returnedConfig)
			})

			got, _, err := client.Registries.DockerCredentials(ctx, testRegistry, test.params)
			fmt.Println(returnedConfig)
			require.NoError(t, err)
			require.Equal(t, []byte(returnedConfig), got.DockerConfigJSON)
		})
	}
}
