// Copyright 2019 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 mutate_test

import (
	"log"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/google/go-containerregistry/pkg/v1/empty"
	"github.com/google/go-containerregistry/pkg/v1/mutate"
	"github.com/google/go-containerregistry/pkg/v1/partial"
	"github.com/google/go-containerregistry/pkg/v1/random"
	"github.com/google/go-containerregistry/pkg/v1/types"
	"github.com/google/go-containerregistry/pkg/v1/validate"
)

func TestAppendIndex(t *testing.T) {
	base, err := random.Index(1024, 3, 3)
	if err != nil {
		t.Fatal(err)
	}
	idx, err := random.Index(2048, 1, 2)
	if err != nil {
		t.Fatal(err)
	}
	img, err := random.Image(4096, 5)
	if err != nil {
		t.Fatal(err)
	}
	l, err := random.Layer(1024, types.OCIUncompressedRestrictedLayer)
	if err != nil {
		t.Fatal(err)
	}

	weirdHash := v1.Hash{
		Algorithm: "sha256",
		Hex:       strings.Repeat("0", 64),
	}

	add := mutate.AppendManifests(base, mutate.IndexAddendum{
		Add: idx,
		Descriptor: v1.Descriptor{
			URLs: []string{"index.example.com"},
		},
	}, mutate.IndexAddendum{
		Add: img,
		Descriptor: v1.Descriptor{
			URLs: []string{"image.example.com"},
		},
	}, mutate.IndexAddendum{
		Add: l,
		Descriptor: v1.Descriptor{
			MediaType: types.MediaType("application/xml"),
			URLs:      []string{"blob.example.com"},
		},
	}, mutate.IndexAddendum{
		Add: l,
		Descriptor: v1.Descriptor{
			URLs:   []string{"layer.example.com"},
			Size:   1337,
			Digest: weirdHash,
			Platform: &v1.Platform{
				OS:           "haiku",
				Architecture: "toaster",
			},
			Annotations: map[string]string{"weird": "true"},
		},
	})

	if err := validate.Index(add); err != nil {
		t.Errorf("Validate() = %v", err)
	}

	got, err := add.MediaType()
	if err != nil {
		t.Fatal(err)
	}
	want, err := base.MediaType()
	if err != nil {
		t.Fatal(err)
	}
	if got != want {
		t.Errorf("MediaType() = %s != %s", got, want)
	}

	// TODO(jonjohnsonjr): There's no way to grab layers from v1.ImageIndex.
	m, err := add.IndexManifest()
	if err != nil {
		log.Fatal(err)
	}

	for i, want := range map[int]string{
		3: "index.example.com",
		4: "image.example.com",
		5: "blob.example.com",
		6: "layer.example.com",
	} {
		if got := m.Manifests[i].URLs[0]; got != want {
			t.Errorf("wrong URLs[0] for Manifests[%d]: %s != %s", i, got, want)
		}
	}

	if got, want := m.Manifests[5].MediaType, types.MediaType("application/xml"); got != want {
		t.Errorf("wrong MediaType for layer: %s != %s", got, want)
	}

	if got, want := m.Manifests[6].MediaType, types.OCIUncompressedRestrictedLayer; got != want {
		t.Errorf("wrong MediaType for layer: %s != %s", got, want)
	}

	// Append the index to itself and make sure it still validates.
	add = mutate.AppendManifests(add, mutate.IndexAddendum{
		Add: add,
	})
	if err := validate.Index(add); err != nil {
		t.Errorf("Validate() = %v", err)
	}

	// Wrap the whole thing in another index and make sure it still validates.
	add = mutate.AppendManifests(empty.Index, mutate.IndexAddendum{
		Add: add,
	})
	if err := validate.Index(add); err != nil {
		t.Errorf("Validate() = %v", err)
	}
}

func TestIndexImmutability(t *testing.T) {
	base, err := random.Index(1024, 3, 3)
	if err != nil {
		t.Fatal(err)
	}
	ii, err := random.Index(2048, 1, 2)
	if err != nil {
		t.Fatal(err)
	}
	i, err := random.Image(4096, 5)
	if err != nil {
		t.Fatal(err)
	}
	idx := mutate.AppendManifests(base, mutate.IndexAddendum{
		Add: ii,
		Descriptor: v1.Descriptor{
			URLs: []string{"index.example.com"},
		},
	}, mutate.IndexAddendum{
		Add: i,
		Descriptor: v1.Descriptor{
			URLs: []string{"image.example.com"},
		},
	})

	t.Run("index manifest", func(t *testing.T) {
		// Check that Manifest is immutable.
		changed, err := idx.IndexManifest()
		if err != nil {
			t.Errorf("IndexManifest() = %v", err)
		}
		want := changed.DeepCopy() // Create a copy of original before mutating it.
		changed.MediaType = types.DockerManifestList

		if got, err := idx.IndexManifest(); err != nil {
			t.Errorf("IndexManifest() = %v", err)
		} else if !cmp.Equal(got, want) {
			t.Errorf("IndexManifest changed! %s", cmp.Diff(got, want))
		}
	})
}

// TestAppend_ArtifactType tests that appending an image manifest to an
// index results in the image's artifactType being set in the descriptor
// inside the index. Per the OCI distribution spec, if artifactType is
// not set in the manifest, it falls back to config.mediaType.
func TestAppend_ArtifactType(t *testing.T) {
	for _, c := range []struct {
		desc, configMediaType, wantArtifactType string
	}{{
		desc:             "standard config.mediaType, artifactType falls back to config.mediaType",
		configMediaType:  string(types.DockerConfigJSON),
		wantArtifactType: string(types.DockerConfigJSON),
	}, {
		desc:             "non-standard config.mediaType, want artifactType",
		configMediaType:  "application/vnd.custom.something",
		wantArtifactType: "application/vnd.custom.something",
	}} {
		t.Run(c.desc, func(t *testing.T) {
			img, err := random.Image(1, 1)
			if err != nil {
				t.Fatalf("random.Image: %v", err)
			}
			img = mutate.ConfigMediaType(img, types.MediaType(c.configMediaType))
			idx := mutate.AppendManifests(empty.Index, mutate.IndexAddendum{
				Add: img,
			})
			mf, err := idx.IndexManifest()
			if err != nil {
				t.Fatalf("IndexManifest: %v", err)
			}
			if got := mf.Manifests[0].ArtifactType; got != c.wantArtifactType {
				t.Errorf("manifest artifactType: got %q, want %q", got, c.wantArtifactType)
			}

			desc, err := partial.Descriptor(img)
			if err != nil {
				t.Fatalf("partial.Descriptor: %v", err)
			}
			if got := desc.ArtifactType; got != c.wantArtifactType {
				t.Errorf("descriptor artifactType: got %q, want %q", got, c.wantArtifactType)
			}

			gotAT, err := partial.ArtifactType(img)
			if err != nil {
				t.Fatalf("partial.ArtifactType: %v", err)
			}
			if gotAT != c.wantArtifactType {
				t.Errorf("partial.ArtifactType: got %q, want %q", gotAT, c.wantArtifactType)
			}
		})
	}
}
