package oci

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path"
	"path/filepath"
	"testing"

	"github.com/containerd/containerd/v2/client"
	"github.com/containerd/containerd/v2/core/content"
	"github.com/containerd/containerd/v2/core/images"
	"github.com/containerd/containerd/v2/core/metadata"
	"github.com/containerd/containerd/v2/pkg/namespaces"
	"github.com/containerd/containerd/v2/plugins/content/local"
	"github.com/opencontainers/go-digest"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/stretchr/testify/require"
	bolt "go.etcd.io/bbolt"
)

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

	b, err := os.ReadFile("./testdata/images.json")
	require.NoError(t, err)
	imgs := []map[string]string{}
	err = json.Unmarshal(b, &imgs)
	require.NoError(t, err)
	blobs := map[digest.Digest][]byte{}
	fileItems, err := os.ReadDir("./testdata/blobs/sha256")
	require.NoError(t, err)
	for _, item := range fileItems {
		if item.IsDir() {
			continue
		}
		dgst, err := digest.Parse(fmt.Sprintf("sha256:%s", item.Name()))
		require.NoError(t, err)
		b, err := os.ReadFile(path.Join("./testdata/blobs/sha256", item.Name()))
		require.NoError(t, err)
		blobs[dgst] = b
	}

	contentPath := t.TempDir()
	contentStore, err := local.NewStore(contentPath)
	require.NoError(t, err)
	boltDB, err := bolt.Open(path.Join(t.TempDir(), "bolt.db"), 0o644, nil)
	require.NoError(t, err)
	db := metadata.NewDB(boltDB, contentStore, nil)
	imageStore := metadata.NewImageStore(db)
	ctx := namespaces.WithNamespace(t.Context(), "k8s.io")
	for _, img := range imgs {
		dgst, err := digest.Parse(img["digest"])
		require.NoError(t, err)
		cImg := images.Image{
			Name: img["name"],
			Target: ocispec.Descriptor{
				MediaType: img["mediaType"],
				Digest:    dgst,
				Size:      int64(len(blobs[dgst])),
			},
		}
		_, err = imageStore.Create(ctx, cImg)
		require.NoError(t, err)
	}
	for k, v := range blobs {
		writer, err := contentStore.Writer(ctx, content.WithRef(k.String()))
		require.NoError(t, err)
		_, err = writer.Write(v)
		require.NoError(t, err)
		err = writer.Commit(ctx, int64(len(v)), k)
		require.NoError(t, err)
		writer.Close()
	}
	containerdClient, err := client.New("", client.WithServices(client.WithImageStore(imageStore), client.WithContentStore(contentStore)))
	require.NoError(t, err)
	remoteContainerd := &Containerd{
		client: containerdClient,
	}
	localContainerd := &Containerd{
		contentPath: contentPath,
		client:      containerdClient,
	}

	memoryClient := NewMemory()
	for _, img := range imgs {
		dgst, err := digest.Parse(img["digest"])
		require.NoError(t, err)
		img, err := ParseImageRequireDigest(img["name"], dgst)
		require.NoError(t, err)
		memoryClient.AddImage(img)
	}
	for k, v := range blobs {
		memoryClient.AddBlob(v, k)
	}

	for _, ociClient := range []Client{remoteContainerd, localContainerd, memoryClient} {
		t.Run(ociClient.Name(), func(t *testing.T) {
			t.Parallel()

			b, mt, err := ociClient.GetManifest(ctx, digest.FromString("foo"))
			require.Empty(t, b)
			require.Empty(t, mt)
			require.ErrorIs(t, err, ErrNotFound)
			rc, err := ociClient.GetBlob(ctx, digest.FromString("foo"))
			require.Empty(t, rc)
			require.ErrorIs(t, err, ErrNotFound)
			size, err := ociClient.Size(ctx, digest.FromString("foo"))
			require.Empty(t, size)
			require.ErrorIs(t, err, ErrNotFound)

			imgs, err := ociClient.ListImages(ctx)
			require.NoError(t, err)
			require.Len(t, imgs, 5)
			for _, img := range imgs {
				tagName, ok := img.TagName()
				require.True(t, ok)
				_, err := ociClient.Resolve(ctx, tagName)
				require.NoError(t, err)
			}

			noPlatformImg := Image{
				Registry:   "example.com",
				Repository: "org/no-platform",
				Tag:        "test",
			}
			tagName, ok := noPlatformImg.TagName()
			require.True(t, ok)
			dgst, err := ociClient.Resolve(ctx, tagName)
			require.NoError(t, err)
			noPlatformImg.Digest = dgst
			_, err = WalkImage(ctx, ociClient, noPlatformImg)
			require.EqualError(t, err, "failed to walk image manifests: could not find any platforms with local content in manifest sha256:addc990c58744bdf96364fe89bd4aab38b1e824d51c688edb36c75247cd45fa9")

			contentTests := []struct {
				mediaType string
				dgst      digest.Digest
				size      int64
			}{
				{
					mediaType: ocispec.MediaTypeImageIndex,
					dgst:      digest.Digest("sha256:9430beb291fa7b96997711fc486bc46133c719631aefdbeebe58dd3489217bfe"),
					size:      374,
				},
				{
					mediaType: ocispec.MediaTypeImageManifest,
					dgst:      digest.Digest("sha256:aec8273a5e5aca369fcaa8cecef7bf6c7959d482f5c8cfa2236a6a16e46bbdcf"),
					size:      476,
				},
				{
					mediaType: ocispec.MediaTypeImageConfig,
					dgst:      digest.Digest("sha256:68b8a989a3e08ddbdb3a0077d35c0d0e59c9ecf23d0634584def8bdbb7d6824f"),
					size:      529,
				},
				{
					mediaType: ocispec.MediaTypeImageLayer,
					dgst:      digest.Digest("sha256:3caa2469de2a23cbcc209dd0b9d01cd78ff9a0f88741655991d36baede5b0996"),
					size:      118,
				},
			}
			for _, tt := range contentTests {
				t.Run(tt.mediaType, func(t *testing.T) {
					t.Parallel()

					size, err := ociClient.Size(ctx, tt.dgst)
					require.NoError(t, err)
					require.Equal(t, tt.size, size)
					if tt.mediaType != ocispec.MediaTypeImageLayer {
						b, mediaType, err := ociClient.GetManifest(ctx, tt.dgst)
						require.NoError(t, err)
						require.Equal(t, tt.mediaType, mediaType)
						require.Equal(t, blobs[tt.dgst], b)
					} else {
						rc, err := ociClient.GetBlob(ctx, tt.dgst)
						require.NoError(t, err)
						defer rc.Close()
						b, err := io.ReadAll(rc)
						require.NoError(t, err)
						require.Equal(t, blobs[tt.dgst], b)
					}
				})
			}

			identifiersTests := []struct {
				imageName    string
				imageDigest  string
				expectedKeys []string
			}{
				{
					imageName:   "ghcr.io/spegel-org/spegel:v0.0.8-with-media-type",
					imageDigest: "sha256:9506c8e7a2d0a098d43cadfd7ecdc3c91697e8188d3a1245943b669f717747b4",
					expectedKeys: []string{
						"sha256:9506c8e7a2d0a098d43cadfd7ecdc3c91697e8188d3a1245943b669f717747b4",
						"sha256:44cb2cf712c060f69df7310e99339c1eb51a085446f1bb6d44469acff35b4355",
						"sha256:d715ba0d85ee7d37da627d0679652680ed2cb23dde6120f25143a0b8079ee47e",
						"sha256:a7ca0d9ba68fdce7e15bc0952d3e898e970548ca24d57698725836c039086639",
						"sha256:fe5ca62666f04366c8e7f605aa82997d71320183e99962fa76b3209fdfbb8b58",
						"sha256:b02a7525f878e61fc1ef8a7405a2cc17f866e8de222c1c98fd6681aff6e509db",
						"sha256:fcb6f6d2c9986d9cd6a2ea3cc2936e5fc613e09f1af9042329011e43057f3265",
						"sha256:e8c73c638ae9ec5ad70c49df7e484040d889cca6b4a9af056579c3d058ea93f0",
						"sha256:1e3d9b7d145208fa8fa3ee1c9612d0adaac7255f1bbc9ddea7e461e0b317805c",
						"sha256:4aa0ea1413d37a58615488592a0b827ea4b2e48fa5a77cf707d0e35f025e613f",
						"sha256:7c881f9ab25e0d86562a123b5fb56aebf8aa0ddd7d48ef602faf8d1e7cf43d8c",
						"sha256:5627a970d25e752d971a501ec7e35d0d6fdcd4a3ce9e958715a686853024794a",
						"sha256:76f3a495ffdc00c612747ba0c59fc56d0a2610d2785e80e9edddbf214c2709ef",
						"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1",
						"sha256:0ad7c556c55464fa44d4c41e5236715e015b0266daced62140fb5c6b983c946b",
						"sha256:1079836371d57a148a0afa5abfe00bd91825c869fcc6574a418f4371d53cab4c",
						"sha256:b437b30b8b4cc4e02865517b5ca9b66501752012a028e605da1c98beb0ed9f50",
						"sha256:fe5ca62666f04366c8e7f605aa82997d71320183e99962fa76b3209fdfbb8b58",
						"sha256:b02a7525f878e61fc1ef8a7405a2cc17f866e8de222c1c98fd6681aff6e509db",
						"sha256:fcb6f6d2c9986d9cd6a2ea3cc2936e5fc613e09f1af9042329011e43057f3265",
						"sha256:e8c73c638ae9ec5ad70c49df7e484040d889cca6b4a9af056579c3d058ea93f0",
						"sha256:1e3d9b7d145208fa8fa3ee1c9612d0adaac7255f1bbc9ddea7e461e0b317805c",
						"sha256:4aa0ea1413d37a58615488592a0b827ea4b2e48fa5a77cf707d0e35f025e613f",
						"sha256:7c881f9ab25e0d86562a123b5fb56aebf8aa0ddd7d48ef602faf8d1e7cf43d8c",
						"sha256:5627a970d25e752d971a501ec7e35d0d6fdcd4a3ce9e958715a686853024794a",
						"sha256:01d28554416aa05390e2827a653a1289a2a549e46cc78d65915a75377c6008ba",
						"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1",
						"sha256:dce623533c59af554b85f859e91fc1cbb7f574e873c82f36b9ea05a09feb0b53",
						"sha256:c73129c9fb699b620aac2df472196ed41797fd0f5a90e1942bfbf19849c4a1c9",
						"sha256:0b41f743fd4d78cb50ba86dd3b951b51458744109e1f5063a76bc5a792c3d8e7",
						"sha256:fe5ca62666f04366c8e7f605aa82997d71320183e99962fa76b3209fdfbb8b58",
						"sha256:b02a7525f878e61fc1ef8a7405a2cc17f866e8de222c1c98fd6681aff6e509db",
						"sha256:fcb6f6d2c9986d9cd6a2ea3cc2936e5fc613e09f1af9042329011e43057f3265",
						"sha256:e8c73c638ae9ec5ad70c49df7e484040d889cca6b4a9af056579c3d058ea93f0",
						"sha256:1e3d9b7d145208fa8fa3ee1c9612d0adaac7255f1bbc9ddea7e461e0b317805c",
						"sha256:4aa0ea1413d37a58615488592a0b827ea4b2e48fa5a77cf707d0e35f025e613f",
						"sha256:7c881f9ab25e0d86562a123b5fb56aebf8aa0ddd7d48ef602faf8d1e7cf43d8c",
						"sha256:5627a970d25e752d971a501ec7e35d0d6fdcd4a3ce9e958715a686853024794a",
						"sha256:0dc769edeab7d9f622b9703579f6c89298a4cf45a84af1908e26fffca55341e1",
						"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1",
					},
				},
				{
					imageName:   "ghcr.io/spegel-org/spegel:v0.0.8-without-media-type",
					imageDigest: "sha256:d8df04365d06181f037251de953aca85cc16457581a8fc168f4957c978e1008b",
					expectedKeys: []string{
						"sha256:d8df04365d06181f037251de953aca85cc16457581a8fc168f4957c978e1008b",
						"sha256:44cb2cf712c060f69df7310e99339c1eb51a085446f1bb6d44469acff35b4355",
						"sha256:d715ba0d85ee7d37da627d0679652680ed2cb23dde6120f25143a0b8079ee47e",
						"sha256:a7ca0d9ba68fdce7e15bc0952d3e898e970548ca24d57698725836c039086639",
						"sha256:fe5ca62666f04366c8e7f605aa82997d71320183e99962fa76b3209fdfbb8b58",
						"sha256:b02a7525f878e61fc1ef8a7405a2cc17f866e8de222c1c98fd6681aff6e509db",
						"sha256:fcb6f6d2c9986d9cd6a2ea3cc2936e5fc613e09f1af9042329011e43057f3265",
						"sha256:e8c73c638ae9ec5ad70c49df7e484040d889cca6b4a9af056579c3d058ea93f0",
						"sha256:1e3d9b7d145208fa8fa3ee1c9612d0adaac7255f1bbc9ddea7e461e0b317805c",
						"sha256:4aa0ea1413d37a58615488592a0b827ea4b2e48fa5a77cf707d0e35f025e613f",
						"sha256:7c881f9ab25e0d86562a123b5fb56aebf8aa0ddd7d48ef602faf8d1e7cf43d8c",
						"sha256:5627a970d25e752d971a501ec7e35d0d6fdcd4a3ce9e958715a686853024794a",
						"sha256:76f3a495ffdc00c612747ba0c59fc56d0a2610d2785e80e9edddbf214c2709ef",
						"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1",
						"sha256:0ad7c556c55464fa44d4c41e5236715e015b0266daced62140fb5c6b983c946b",
						"sha256:1079836371d57a148a0afa5abfe00bd91825c869fcc6574a418f4371d53cab4c",
						"sha256:b437b30b8b4cc4e02865517b5ca9b66501752012a028e605da1c98beb0ed9f50",
						"sha256:fe5ca62666f04366c8e7f605aa82997d71320183e99962fa76b3209fdfbb8b58",
						"sha256:b02a7525f878e61fc1ef8a7405a2cc17f866e8de222c1c98fd6681aff6e509db",
						"sha256:fcb6f6d2c9986d9cd6a2ea3cc2936e5fc613e09f1af9042329011e43057f3265",
						"sha256:e8c73c638ae9ec5ad70c49df7e484040d889cca6b4a9af056579c3d058ea93f0",
						"sha256:1e3d9b7d145208fa8fa3ee1c9612d0adaac7255f1bbc9ddea7e461e0b317805c",
						"sha256:4aa0ea1413d37a58615488592a0b827ea4b2e48fa5a77cf707d0e35f025e613f",
						"sha256:7c881f9ab25e0d86562a123b5fb56aebf8aa0ddd7d48ef602faf8d1e7cf43d8c",
						"sha256:5627a970d25e752d971a501ec7e35d0d6fdcd4a3ce9e958715a686853024794a",
						"sha256:01d28554416aa05390e2827a653a1289a2a549e46cc78d65915a75377c6008ba",
						"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1",
						"sha256:dce623533c59af554b85f859e91fc1cbb7f574e873c82f36b9ea05a09feb0b53",
						"sha256:c73129c9fb699b620aac2df472196ed41797fd0f5a90e1942bfbf19849c4a1c9",
						"sha256:0b41f743fd4d78cb50ba86dd3b951b51458744109e1f5063a76bc5a792c3d8e7",
						"sha256:fe5ca62666f04366c8e7f605aa82997d71320183e99962fa76b3209fdfbb8b58",
						"sha256:b02a7525f878e61fc1ef8a7405a2cc17f866e8de222c1c98fd6681aff6e509db",
						"sha256:fcb6f6d2c9986d9cd6a2ea3cc2936e5fc613e09f1af9042329011e43057f3265",
						"sha256:e8c73c638ae9ec5ad70c49df7e484040d889cca6b4a9af056579c3d058ea93f0",
						"sha256:1e3d9b7d145208fa8fa3ee1c9612d0adaac7255f1bbc9ddea7e461e0b317805c",
						"sha256:4aa0ea1413d37a58615488592a0b827ea4b2e48fa5a77cf707d0e35f025e613f",
						"sha256:7c881f9ab25e0d86562a123b5fb56aebf8aa0ddd7d48ef602faf8d1e7cf43d8c",
						"sha256:5627a970d25e752d971a501ec7e35d0d6fdcd4a3ce9e958715a686853024794a",
						"sha256:0dc769edeab7d9f622b9703579f6c89298a4cf45a84af1908e26fffca55341e1",
						"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1",
					},
				},
			}
			for _, tt := range identifiersTests {
				t.Run(tt.imageName, func(t *testing.T) {
					t.Parallel()

					img, err := ParseImageRequireDigest(tt.imageName, digest.Digest(tt.imageDigest))
					require.NoError(t, err)
					keys, err := WalkImage(ctx, ociClient, img)
					require.NoError(t, err)
					require.Equal(t, tt.expectedKeys, keys)
				})
			}
		})
	}
}

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

	tests := []struct {
		name              string
		dgst              digest.Digest
		expectedMediaType string
	}{
		{
			name:              "image config",
			dgst:              digest.Digest("sha256:68b8a989a3e08ddbdb3a0077d35c0d0e59c9ecf23d0634584def8bdbb7d6824f"),
			expectedMediaType: ocispec.MediaTypeImageConfig,
		},
		{
			name:              "image index",
			dgst:              digest.Digest("sha256:9430beb291fa7b96997711fc486bc46133c719631aefdbeebe58dd3489217bfe"),
			expectedMediaType: ocispec.MediaTypeImageIndex,
		},
		{
			name:              "image index without media type",
			dgst:              digest.Digest("sha256:d8df04365d06181f037251de953aca85cc16457581a8fc168f4957c978e1008b"),
			expectedMediaType: ocispec.MediaTypeImageIndex,
		},
		{
			name:              "image manifest",
			dgst:              digest.Digest("sha256:dce623533c59af554b85f859e91fc1cbb7f574e873c82f36b9ea05a09feb0b53"),
			expectedMediaType: ocispec.MediaTypeImageManifest,
		},
		{
			name:              "image manifest without media type",
			dgst:              digest.Digest("sha256:b6d6089ca6c395fd563c2084f5dd7bc56a2f5e6a81413558c5be0083287a77e9"),
			expectedMediaType: ocispec.MediaTypeImageManifest,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			b, err := os.ReadFile(filepath.Join("testdata", "blobs", tt.dgst.Algorithm().String(), tt.dgst.Encoded()))
			require.NoError(t, err)
			mt, err := DetermineMediaType(b)
			require.NoError(t, err)
			require.Equal(t, tt.expectedMediaType, mt)
		})
	}

	mt, err := DetermineMediaType([]byte("{}"))
	require.EqualError(t, err, "not able to determine media type")
	require.Empty(t, mt)
}