// 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 tarball

import (
	"bytes"
	"compress/gzip"
	"io"
	"os"
	"testing"

	"github.com/google/go-containerregistry/pkg/compression"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/google/go-containerregistry/pkg/v1/compare"
	"github.com/google/go-containerregistry/pkg/v1/types"
	"github.com/google/go-containerregistry/pkg/v1/validate"
)

func TestLayerFromFile(t *testing.T) {
	setupFixtures(t)
	defer teardownFixtures(t)

	tarLayer, err := LayerFromFile("testdata/content.tar")
	if err != nil {
		t.Fatalf("Unable to create layer from tar file: %v", err)
	}

	tarGzLayer, err := LayerFromFile("gzip_content.tgz")
	if err != nil {
		t.Fatalf("Unable to create layer from compressed tar file: %v", err)
	}

	tarZstdLayer, err := LayerFromFile("zstd_content.tar.zst")
	if err != nil {
		t.Fatalf("Unable to create layer from compressed tar file: %v", err)
	}

	if err := compare.Layers(tarLayer, tarGzLayer); err != nil {
		t.Errorf("compare.Layers: %v", err)
	}

	if err := compare.Layers(tarLayer, tarZstdLayer); err != nil {
		t.Errorf("compare.Layers: %v", err)
	}

	if err := validate.Layer(tarLayer); err != nil {
		t.Errorf("validate.Layer(tarLayer): %v", err)
	}

	if err := validate.Layer(tarGzLayer); err != nil {
		t.Errorf("validate.Layer(tarGzLayer): %v", err)
	}

	if err := validate.Layer(tarZstdLayer); err != nil {
		t.Errorf("validate.Layer(tarZstdLayer): %v", err)
	}

	getTestDigest := func(testName string, opts ...LayerOption) v1.Hash {
		layer, err := LayerFromFile("testdata/content.tar", opts...)
		if err != nil {
			t.Fatalf("Unable to create layer with '%s' compression from tar file: %v", testName, err)
		}

		digest, err := layer.Digest()
		if err != nil {
			t.Fatalf("Unable to generate digest with '%s' compression: %v", testName, err)
		}

		return digest
	}

	defaultDigest := getTestDigest("Gzip Default", WithCompressionLevel(gzip.DefaultCompression))
	speedDigest := getTestDigest("Gzip BestSpeed", WithCompressionLevel(gzip.BestSpeed))
	zstdDigest := getTestDigest("Zstd Default", WithCompression(compression.ZStd))
	zstdDigest1 := getTestDigest("Zstd BestSpeed", WithCompression(compression.ZStd), WithCompressionLevel(1))

	if defaultDigest.String() == speedDigest.String() {
		t.Errorf("expected digests to differ: %s", defaultDigest.String())
	}

	if defaultDigest.String() == zstdDigest.String() {
		t.Errorf("expected digests to differ: %s", defaultDigest.String())
	}

	if defaultDigest.String() == zstdDigest1.String() {
		t.Errorf("expected digests to differ: %s", defaultDigest.String())
	}
}

func TestLayerFromOpenerReader(t *testing.T) {
	setupFixtures(t)
	defer teardownFixtures(t)

	ucBytes, err := os.ReadFile("testdata/content.tar")
	if err != nil {
		t.Fatalf("Unable to read tar file: %v", err)
	}
	count := 0
	ucOpener := func() (io.ReadCloser, error) {
		count++
		return io.NopCloser(bytes.NewReader(ucBytes)), nil
	}
	tarLayer, err := LayerFromOpener(ucOpener, WithCompressedCaching)
	if err != nil {
		t.Fatal("Unable to create layer from tar file:", err)
	}
	for i := 0; i < 10; i++ {
		tarLayer.Compressed()
	}

	// Store the count and reset the counter.
	cachedCount := count
	count = 0

	tarLayer, err = LayerFromOpener(ucOpener)
	if err != nil {
		t.Fatal("Unable to create layer from tar file:", err)
	}
	for i := 0; i < 10; i++ {
		tarLayer.Compressed()
	}

	// We expect three calls: gzip sniff, diffid computation, cached compression
	if cachedCount != 3 {
		t.Errorf("cached count = %d, wanted %d", cachedCount, 3)
	}
	if cachedCount+10 != count {
		t.Errorf("count = %d, wanted %d", count, cachedCount+10)
	}

	gzBytes, err := os.ReadFile("gzip_content.tgz")
	if err != nil {
		t.Fatalf("Unable to read tar file: %v", err)
	}
	gzOpener := func() (io.ReadCloser, error) {
		return io.NopCloser(bytes.NewReader(gzBytes)), nil
	}
	tarGzLayer, err := LayerFromOpener(gzOpener)
	if err != nil {
		t.Fatalf("Unable to create layer from tar file: %v", err)
	}

	if err := compare.Layers(tarLayer, tarGzLayer); err != nil {
		t.Errorf("compare.Layers: %v", err)
	}

	zstdBytes, err := os.ReadFile("zstd_content.tar.zst")
	if err != nil {
		t.Fatalf("Unable to read tar file: %v", err)
	}
	zstdOpener := func() (io.ReadCloser, error) {
		return io.NopCloser(bytes.NewReader(zstdBytes)), nil
	}
	tarZstdLayer, err := LayerFromOpener(zstdOpener)
	if err != nil {
		t.Fatalf("Unable to create layer from tar file: %v", err)
	}

	if err := compare.Layers(tarLayer, tarZstdLayer); err != nil {
		t.Errorf("compare.Layers: %v", err)
	}
}

func TestWithMediaType(t *testing.T) {
	setupFixtures(t)
	defer teardownFixtures(t)

	l, err := LayerFromFile("testdata/content.tar")
	if err != nil {
		t.Fatalf("Unable to create layer from tar file: %v", err)
	}
	got, err := l.MediaType()
	if err != nil {
		t.Fatalf("MediaType: %v", err)
	}
	if want := types.DockerLayer; got != want {
		t.Errorf("got %v, want %v", got, want)
	}

	l, err = LayerFromFile("testdata/content.tar", WithMediaType(types.OCILayer))
	if err != nil {
		t.Fatalf("Unable to create layer from tar file: %v", err)
	}
	got, err = l.MediaType()
	if err != nil {
		t.Fatalf("MediaType: %v", err)
	}
	if want := types.OCILayer; got != want {
		t.Errorf("got %v, want %v", got, want)
	}
}

func TestLayerFromReader(t *testing.T) {
	setupFixtures(t)
	defer teardownFixtures(t)

	ucBytes, err := os.ReadFile("testdata/content.tar")
	if err != nil {
		t.Fatalf("Unable to read tar file: %v", err)
	}
	tarLayer, err := LayerFromReader(bytes.NewReader(ucBytes))
	if err != nil {
		t.Fatalf("Unable to create layer from tar file: %v", err)
	}

	gzBytes, err := os.ReadFile("gzip_content.tgz")
	if err != nil {
		t.Fatalf("Unable to read tar file: %v", err)
	}
	tarGzLayer, err := LayerFromReader(bytes.NewReader(gzBytes))
	if err != nil {
		t.Fatalf("Unable to create layer from tar file: %v", err)
	}

	if err := compare.Layers(tarLayer, tarGzLayer); err != nil {
		t.Errorf("compare.Layers: %v", err)
	}

	zstdBytes, err := os.ReadFile("zstd_content.tar.zst")
	if err != nil {
		t.Fatalf("Unable to read tar file: %v", err)
	}
	tarZstdLayer, err := LayerFromReader(bytes.NewReader(zstdBytes))
	if err != nil {
		t.Fatalf("Unable to create layer from tar file: %v", err)
	}

	if err := compare.Layers(tarLayer, tarZstdLayer); err != nil {
		t.Errorf("compare.Layers: %v", err)
	}
}

// Compression settings matter in order for the digest, size,
// compressed assertions to pass
//
// Since our gzip.GzipReadCloser uses gzip.BestSpeed
// we need our fixture to use the same - bazel's pkg_tar doesn't
// seem to let you control compression settings
func setupFixtures(t *testing.T) {
	t.Helper()

	setupCompressedTar(t, "gzip_content.tgz")
	setupCompressedTar(t, "zstd_content.tar.zst")
}

func setupCompressedTar(t *testing.T, fileName string) {
	t.Helper()
	in, err := os.Open("testdata/content.tar")
	if err != nil {
		t.Errorf("Error setting up fixtures: %v", err)
	}

	defer in.Close()

	out, err := os.Create(fileName)
	if err != nil {
		t.Errorf("Error setting up fixtures: %v", err)
	}

	defer out.Close()

	gw, _ := gzip.NewWriterLevel(out, gzip.BestSpeed)
	defer gw.Close()

	_, err = io.Copy(gw, in)
	if err != nil {
		t.Errorf("Error setting up fixtures: %v", err)
	}
}

func teardownFixtures(t *testing.T) {
	t.Helper()
	if err := os.Remove("gzip_content.tgz"); err != nil {
		t.Errorf("Error tearing down fixtures: %v", err)
	}
	if err := os.Remove("zstd_content.tar.zst"); err != nil {
		t.Errorf("Error tearing down fixtures: %v", err)
	}
}
