package oci

import (
	"fmt"
	"testing"

	digest "github.com/opencontainers/go-digest"
	"github.com/stretchr/testify/require"
)

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

	tests := []struct {
		name               string
		image              string
		expectedRepository string
		expectedTag        string
		expectedString     string
		expectedDigest     digest.Digest
		expectedIsLatest   bool
		digestInImage      bool
	}{
		{
			name:               "Latest tag",
			image:              "library/ubuntu:latest",
			digestInImage:      false,
			expectedRepository: "library/ubuntu",
			expectedTag:        "latest",
			expectedDigest:     digest.Digest("sha256:c0669ef34cdc14332c0f1ab0c2c01acb91d96014b172f1a76f3a39e63d1f0bda"),
			expectedIsLatest:   true,
			expectedString:     "library/ubuntu:latest@sha256:c0669ef34cdc14332c0f1ab0c2c01acb91d96014b172f1a76f3a39e63d1f0bda",
		},
		{
			name:               "Only tag",
			image:              "library/alpine:3.18.0",
			digestInImage:      false,
			expectedRepository: "library/alpine",
			expectedTag:        "3.18.0",
			expectedDigest:     digest.Digest("sha256:c0669ef34cdc14332c0f1ab0c2c01acb91d96014b172f1a76f3a39e63d1f0bda"),
			expectedIsLatest:   false,
			expectedString:     "library/alpine:3.18.0@sha256:c0669ef34cdc14332c0f1ab0c2c01acb91d96014b172f1a76f3a39e63d1f0bda",
		},
		{
			name:               "Tag and digest",
			image:              "jetstack/cert-manager-controller:3.18.0@sha256:c0669ef34cdc14332c0f1ab0c2c01acb91d96014b172f1a76f3a39e63d1f0bda",
			digestInImage:      true,
			expectedRepository: "jetstack/cert-manager-controller",
			expectedTag:        "3.18.0",
			expectedDigest:     digest.Digest("sha256:c0669ef34cdc14332c0f1ab0c2c01acb91d96014b172f1a76f3a39e63d1f0bda"),
			expectedIsLatest:   false,
			expectedString:     "jetstack/cert-manager-controller:3.18.0@sha256:c0669ef34cdc14332c0f1ab0c2c01acb91d96014b172f1a76f3a39e63d1f0bda",
		},
		{
			name:               "Only digest",
			image:              "fluxcd/helm-controller@sha256:c0669ef34cdc14332c0f1ab0c2c01acb91d96014b172f1a76f3a39e63d1f0bda",
			digestInImage:      true,
			expectedRepository: "fluxcd/helm-controller",
			expectedTag:        "",
			expectedDigest:     digest.Digest("sha256:c0669ef34cdc14332c0f1ab0c2c01acb91d96014b172f1a76f3a39e63d1f0bda"),
			expectedIsLatest:   false,
			expectedString:     "fluxcd/helm-controller@sha256:c0669ef34cdc14332c0f1ab0c2c01acb91d96014b172f1a76f3a39e63d1f0bda",
		},
		{
			name:               "Digest only in extra digest",
			image:              "foo/bar",
			digestInImage:      false,
			expectedRepository: "foo/bar",
			expectedDigest:     digest.Digest("sha256:c0669ef34cdc14332c0f1ab0c2c01acb91d96014b172f1a76f3a39e63d1f0bda"),
			expectedIsLatest:   false,
			expectedString:     "foo/bar@sha256:c0669ef34cdc14332c0f1ab0c2c01acb91d96014b172f1a76f3a39e63d1f0bda",
		},
	}
	registries := []string{"docker.io", "quay.io", "ghcr.com", "127.0.0.1"}
	for _, registry := range registries {
		for _, tt := range tests {
			t.Run(fmt.Sprintf("%s_%s", tt.name, registry), func(t *testing.T) {
				t.Parallel()

				for _, extraDgst := range []string{tt.expectedDigest.String(), ""} {
					img, err := ParseImageRequireDigest(fmt.Sprintf("%s/%s", registry, tt.image), digest.Digest(extraDgst))
					if !tt.digestInImage && extraDgst == "" {
						require.EqualError(t, err, "image needs to contain a digest")
						continue
					}
					require.NoError(t, err)
					require.Equal(t, registry, img.Registry)
					require.Equal(t, tt.expectedRepository, img.Repository)
					require.Equal(t, tt.expectedTag, img.Tag)
					require.Equal(t, tt.expectedDigest, img.Digest)
					require.Equal(t, tt.expectedIsLatest, img.IsLatestTag())
					tagName, ok := img.TagName()
					if tt.expectedTag == "" {
						require.False(t, ok)
						require.Empty(t, tagName)
					} else {
						require.True(t, ok)
						require.Equal(t, registry+"/"+tt.expectedRepository+":"+tt.expectedTag, tagName)
					}
					require.Equal(t, fmt.Sprintf("%s/%s", registry, tt.expectedString), img.String())
				}
			})
		}
	}
}

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

	tests := []struct {
		name          string
		s             string
		dgst          digest.Digest
		expectedError string
	}{
		{
			name:          "digests do not match",
			s:             "quay.io/jetstack/cert-manager-webhook@sha256:13fd9eaadb4e491ef0e1d82de60cb199f5ad2ea5a3f8e0c19fdf31d91175b9cb",
			dgst:          digest.Digest("sha256:ec4306b243d98cce7c3b1f994f2dae660059ef521b2b24588cfdc950bd816d4c"),
			expectedError: "invalid digest set does not match parsed digest: quay.io/jetstack/cert-manager-webhook@sha256:13fd9eaadb4e491ef0e1d82de60cb199f5ad2ea5a3f8e0c19fdf31d91175b9cb sha256:13fd9eaadb4e491ef0e1d82de60cb199f5ad2ea5a3f8e0c19fdf31d91175b9cb",
		},
		{
			name:          "no tag or digest",
			s:             "ghcr.io/clyde-org/clyde",
			dgst:          "",
			expectedError: "image needs to contain a digest",
		},
		{
			name:          "reference contains protocol",
			s:             "https://example.com/test:latest",
			dgst:          "",
			expectedError: "invalid reference",
		},
		{
			name:          "unparsable url",
			s:             "example%#$.com/foo",
			dgst:          "",
			expectedError: "parse \"dummy://example%\": invalid URL escape \"%\"",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			_, err := ParseImageRequireDigest(tt.s, tt.dgst)
			require.EqualError(t, err, tt.expectedError)
		})
	}
}

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

	// TODO (phillebaba): Add test case for no digest or tag. One needs to be set.
	tests := []struct {
		name          string
		registry      string
		repository    string
		tag           string
		dgst          digest.Digest
		expectedError string
	}{
		{
			name:          "missing registry",
			registry:      "",
			repository:    "foo/bar",
			tag:           "latest",
			dgst:          digest.Digest("sha256:ec4306b243d98cce7c3b1f994f2dae660059ef521b2b24588cfdc950bd816d4c"),
			expectedError: "image needs to contain a registry",
		},
		{
			name:          "missing repository",
			registry:      "example.com",
			repository:    "",
			tag:           "latest",
			dgst:          digest.Digest("sha256:ec4306b243d98cce7c3b1f994f2dae660059ef521b2b24588cfdc950bd816d4c"),
			expectedError: "image needs to contain a repository",
		},
		{
			name:          "invalid digest",
			registry:      "example.com",
			repository:    "foo/bar",
			tag:           "latest",
			dgst:          digest.Digest("test"),
			expectedError: "invalid checksum digest format",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			_, err := NewImage(tt.registry, tt.repository, tt.tag, tt.dgst)
			require.EqualError(t, err, tt.expectedError)
		})
	}
}