//go:build !remote

package libimage

import (
	"context"
	"os"
	"path/filepath"
	"testing"

	"github.com/containers/common/pkg/config"
	"github.com/containers/storage"
	"github.com/containers/storage/pkg/ioutils"
	"github.com/stretchr/testify/require"
)

func TestCorruptedLayers(t *testing.T) {
	// Regression tests for https://bugzilla.redhat.com/show_bug.cgi?id=1966872.
	runtime := testNewRuntime(t)
	ctx := context.Background()
	pullOptions := &PullOptions{}
	pullOptions.Writer = os.Stdout

	imageName := "quay.io/libpod/alpine_nginx:latest"

	exists, err := runtime.Exists(imageName)
	require.NoError(t, err, "image does not exist yet")
	require.False(t, exists, "image does not exist yet")

	pulledImages, err := runtime.Pull(ctx, imageName, config.PullPolicyAlways, pullOptions)
	require.NoError(t, err)
	require.Len(t, pulledImages, 1)
	image := pulledImages[0]

	// Inspecting a healthy image should work.
	_, err = image.Inspect(ctx, nil)
	require.NoError(t, err, "inspecting healthy image should work")

	exists, err = runtime.Exists(imageName)
	require.NoError(t, err, "healthy image exists")
	require.True(t, exists, "healthy image exists")

	// Disk usage works.
	_, _, err = runtime.DiskUsage(ctx)
	require.NoError(t, err, "disk usage works on healthy image")

	// Now remove one layer from the layers.json index in the storage.  The
	// image will still be listed in the container storage but attempting
	// to use it will yield "layer not known" errors.
	indexPath := filepath.Join(runtime.store.GraphRoot(), "vfs-layers/layers.json")
	data, err := os.ReadFile(indexPath)
	require.NoError(t, err, "loading layers.json")
	layers := []*storage.Layer{}
	err = json.Unmarshal(data, &layers)
	require.NoError(t, err, "unmarshaling layers.json")
	require.LessOrEqual(t, 1, len(layers), "at least one layer must be present")

	// Now write back the layers without the first layer!
	data, err = json.Marshal(layers[1:])
	require.NoError(t, err, "unmarshaling layers.json")
	err = ioutils.AtomicWriteFile(indexPath, data, 0o600) // nolint
	require.NoError(t, err, "writing back layers.json")

	err = image.reload() // clear the cached data
	require.NoError(t, err)

	// Now inspecting the image must fail!
	_, err = image.Inspect(ctx, nil)
	require.Error(t, err, "inspecting corrupted image should fail")

	err = image.isCorrupted(ctx, imageName)
	require.Error(t, err, "image is corrupted")

	exists, err = runtime.Exists(imageName)
	require.NoError(t, err, "corrupted image exists should not fail")
	require.False(t, exists, "corrupted image should not be marked to exist")

	// Disk usage does not work.
	_, _, err = runtime.DiskUsage(ctx)
	require.Error(t, err, "disk usage does not work on corrupted image")
	require.Contains(t, err.Error(), "exists in local storage but may be corrupted", "disk usage reports corrupted image")

	// Now make sure that pull will detect the corrupted image and repulls
	// if needed which will repair the data corruption.
	pulledImages, err = runtime.Pull(ctx, imageName, config.PullPolicyNewer, pullOptions)
	require.NoError(t, err)
	require.Len(t, pulledImages, 1)
	image = pulledImages[0]

	// Inspecting a repaired image should work.
	_, err = image.Inspect(ctx, nil)
	require.NoError(t, err, "inspecting repaired image should work")
}
