// Copyright 2018 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package name

import (
	"encoding/json"
	"path"
	"reflect"
	"strings"
	"testing"
)

const validDigest = "sha256:deadb33fdeadb33fdeadb33fdeadb33fdeadb33fdeadb33fdeadb33fdeadb33f"

var goodStrictValidationDigestNames = []string{
	"gcr.io/g-convoy/hello-world@" + validDigest,
	"gcr.io/google.com/project-id/hello-world@" + validDigest,
	"us.gcr.io/project-id/sub-repo@" + validDigest,
	"example.text/foo/bar@" + validDigest,
}

var goodStrictValidationTagDigestNames = []string{
	"example.text/foo/bar:latest@" + validDigest,
	"example.text:8443/foo/bar:latest@" + validDigest,
	"example.text/foo/bar:v1.0.0-alpine@" + validDigest,
}

var goodWeakValidationDigestNames = []string{
	"namespace/pathcomponent/image@" + validDigest,
	"library/ubuntu@" + validDigest,
}

var goodWeakValidationTagDigestNames = []string{
	"nginx:latest@" + validDigest,
	"library/nginx:latest@" + validDigest,
}

var badDigestNames = []string{
	"gcr.io/project-id/unknown-alg@unknown:abc123",
	"gcr.io/project-id/wrong-length@sha256:d34db33fd34db33f",
	"gcr.io/project-id/missing-digest@",
	// https://github.com/google/go-containerregistry/issues/1394
	"repo@sha256:" + strings.Repeat(":", 64),
	"repo@sha256:" + strings.Repeat("sh", 32),
	"repo@sha256:" + validDigest + "@" + validDigest,
	"library/nginx:@" + validDigest,
}

func TestNewDigestStrictValidation(t *testing.T) {
	t.Parallel()

	for _, name := range goodStrictValidationDigestNames {
		if digest, err := NewDigest(name, StrictValidation); err != nil {
			t.Errorf("`%s` should be a valid Digest name, got error: %v", name, err)
		} else if digest.Name() != name {
			t.Errorf("`%v` .Name() should reproduce the original name. Wanted: %s Got: %s", digest, name, digest.Name())
		}
	}

	for _, name := range goodStrictValidationTagDigestNames {
		if _, err := NewDigest(name, StrictValidation); err != nil {
			t.Errorf("`%s` should be a valid Digest name, got error: %v", name, err)
		}
	}

	for _, name := range append(goodWeakValidationDigestNames, badDigestNames...) {
		if repo, err := NewDigest(name, StrictValidation); err == nil {
			t.Errorf("`%s` should be an invalid Digest name, got Digest: %#v", name, repo)
		}
	}
}

func TestNewDigest(t *testing.T) {
	t.Parallel()

	for _, name := range append(goodStrictValidationDigestNames, append(goodWeakValidationDigestNames, goodWeakValidationTagDigestNames...)...) {
		if _, err := NewDigest(name, WeakValidation); err != nil {
			t.Errorf("`%s` should be a valid Digest name, got error: %v", name, err)
		}
	}

	for _, name := range badDigestNames {
		if repo, err := NewDigest(name, WeakValidation); err == nil {
			t.Errorf("`%s` should be an invalid Digest name, got Digest: %#v", name, repo)
		}
	}
}

func TestDigestComponents(t *testing.T) {
	t.Parallel()
	testRegistry := "gcr.io"
	testRepository := "project-id/image"
	fullRepo := path.Join(testRegistry, testRepository)

	digestNameStr := testRegistry + "/" + testRepository + "@" + validDigest
	digest, err := NewDigest(digestNameStr, StrictValidation)
	if err != nil {
		t.Fatalf("`%s` should be a valid Digest name, got error: %v", digestNameStr, err)
	}

	if got := digest.String(); got != digestNameStr {
		t.Errorf("String() was incorrect for %v. Wanted: `%s` Got: `%s`", digest, digestNameStr, got)
	}
	if got := digest.Identifier(); got != validDigest {
		t.Errorf("Identifier() was incorrect for %v. Wanted: `%s` Got: `%s`", digest, validDigest, got)
	}
	actualRegistry := digest.RegistryStr()
	if actualRegistry != testRegistry {
		t.Errorf("RegistryStr() was incorrect for %v. Wanted: `%s` Got: `%s`", digest, testRegistry, actualRegistry)
	}
	actualRepository := digest.RepositoryStr()
	if actualRepository != testRepository {
		t.Errorf("RepositoryStr() was incorrect for %v. Wanted: `%s` Got: `%s`", digest, testRepository, actualRepository)
	}
	contextRepo := digest.Context().String()
	if contextRepo != fullRepo {
		t.Errorf("Context().String() was incorrect for %v. Wanted: `%s` Got: `%s`", digest, fullRepo, contextRepo)
	}
	actualDigest := digest.DigestStr()
	if actualDigest != validDigest {
		t.Errorf("DigestStr() was incorrect for %v. Wanted: `%s` Got: `%s`", digest, validDigest, actualDigest)
	}
}

func TestDigestScopes(t *testing.T) {
	t.Parallel()
	testRegistry := "gcr.io"
	testRepo := "project-id/image"
	testAction := "pull"

	expectedScope := strings.Join([]string{"repository", testRepo, testAction}, ":")

	digestNameStr := testRegistry + "/" + testRepo + "@" + validDigest
	digest, err := NewDigest(digestNameStr, StrictValidation)
	if err != nil {
		t.Fatalf("`%s` should be a valid Digest name, got error: %v", digestNameStr, err)
	}

	actualScope := digest.Scope(testAction)
	if actualScope != expectedScope {
		t.Errorf("scope was incorrect for %v. Wanted: `%s` Got: `%s`", digest, expectedScope, actualScope)
	}
}

func TestJSON(t *testing.T) {
	t.Parallel()
	digestNameStr := "gcr.io/project-id/image@" + validDigest
	digest, err := NewDigest(digestNameStr, StrictValidation)
	if err != nil {
		t.Fatalf("`%s` should be a valid Digest name, got error: %v", digestNameStr, err)
	}

	t.Run("string", func(t *testing.T) {
		t.Parallel()
		b, err := json.Marshal(digest)
		if err != nil {
			t.Fatalf("Marshal() failed: %v", err)
		}

		if want := `"` + digestNameStr + `"`; want != string(b) {
			t.Errorf("Marshal() was incorrect. Wanted: `%s` Got: `%s`", want, string(b))
		}

		var out Digest
		if err := json.Unmarshal(b, &out); err != nil {
			t.Fatalf("Unmarshal() failed: %v", err)
		}

		if out.String() != digest.String() {
			t.Errorf("Unmarshaled Digest should be the same as the original. Wanted: `%s` Got: `%s`", digest, out)
		}
	})

	t.Run("map", func(t *testing.T) {
		t.Parallel()
		in := map[string]Digest{
			"a": digest,
		}
		b, err := json.Marshal(in)
		if err != nil {
			t.Fatalf("MarshalJSON() failed: %v", err)
		}

		want := `{"a":"` + digestNameStr + `"}`
		if want != string(b) {
			t.Errorf("Marshal() was incorrect. Wanted: `%s` Got: `%s`", want, string(b))
		}

		var out map[string]Digest
		if err := json.Unmarshal(b, &out); err != nil {
			t.Fatalf("Unmarshal() failed: %v", err)
		}

		if !reflect.DeepEqual(in, out) {
			t.Errorf("Unmarshaled map should be the same as the original. Wanted: `%v` Got: `%v`", in, out)
		}
	})
}
