package storage

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"

	"k8s.io/apimachinery/pkg/labels"

	"github.com/stretchr/testify/require"

	"github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators"
	v1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1"
	"github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/provider"
)

type fakeProvider struct {
	packages []*operators.PackageManifest

	getCalls int
}

func (p *fakeProvider) Get(namespace, name string) (*operators.PackageManifest, error) {
	p.getCalls = p.getCalls + 1

	return p.packages[0], nil
}

func (p *fakeProvider) List(namespace string, selector labels.Selector) (*operators.PackageManifestList, error) {
	return &operators.PackageManifestList{}, nil
}

var _ provider.PackageManifestProvider = &fakeProvider{}

func TestLogoStorageConnect(t *testing.T) {
	provider := fakeProvider{}
	provider.packages = append(provider.packages, testPackage())
	ctx, cancel := context.WithCancel(context.TODO())
	defer cancel()

	storage := NewLogoStorage(v1.Resource("packagemanifests/icon"), &provider)

	rr := httptest.NewRecorder()
	req, err := http.NewRequest("GET", "", nil)
	require.NoError(t, err)

	handler, err := storage.Connect(ctx, "pkg-a", nil, nil)
	require.NoError(t, err)

	handler.ServeHTTP(rr, req)
	require.Equal(t, http.StatusOK, rr.Code)
	require.Equal(t, "image/png", rr.Header().Get("Content-Type"))
	require.NotNil(t, rr.Body)

	cachedRR := httptest.NewRecorder()
	cachedReq, err := http.NewRequest("GET", "?resourceVersion=pkg-a.stable.csv-a", nil)
	require.NoError(t, err)

	etag := rr.Header().Get("Etag")
	require.Equal(t, `"pkg-a.stable.csv-a"`, etag)
	cachedReq.Header.Set("If-None-Match", etag)

	handler.ServeHTTP(cachedRR, cachedReq)
	require.Equal(t, http.StatusNotModified, cachedRR.Code)
	require.Equal(t, 1, provider.getCalls, "PackageManifestProvider.Get() should not be called for cached icon")

	handler.ServeHTTP(rr, req)
	require.Equal(t, http.StatusOK, rr.Code)
	require.Equal(t, 2, provider.getCalls, "PackageManifestProvider.Get() should be called again to fetch icon")
}

func TestLogoStorageConnectDefaultIcon(t *testing.T) {
	provider := fakeProvider{}
	provider.packages = append(provider.packages, testPackage())
	provider.packages[0].Status.Channels = nil
	ctx, cancel := context.WithCancel(context.TODO())
	defer cancel()

	storage := NewLogoStorage(v1.Resource("packagemanifests/icon"), &provider)

	rr := httptest.NewRecorder()
	req, err := http.NewRequest("GET", "", nil)
	require.NoError(t, err)

	handler, err := storage.Connect(ctx, "pkg-a", nil, nil)
	require.NoError(t, err)

	handler.ServeHTTP(rr, req)
	require.Equal(t, http.StatusOK, rr.Code)
	require.Equal(t, "image/svg+xml", rr.Header().Get("Content-Type"))
	require.NotNil(t, rr.Body)
	require.Equal(t, 1, provider.getCalls, "PackageManifestProvider.Get() should be called for missing icon")
}

func testPackage() *operators.PackageManifest {
	return &operators.PackageManifest{
		Status: operators.PackageManifestStatus{
			Channels: []operators.PackageChannel{
				{
					Name:       "stable",
					CurrentCSV: "csv-a",
					CurrentCSVDesc: operators.CSVDescription{
						Icon: []operators.Icon{{Mediatype: "image/png", Base64Data: iconData}},
					},
				},
				{
					Name:       "alpha",
					CurrentCSV: "csv-b",
					CurrentCSVDesc: operators.CSVDescription{
						Icon: []operators.Icon{{Mediatype: "image/png", Base64Data: "different-icon"}},
					},
				},
			},
			DefaultChannel: "stable",
		},
	}
}

const iconData = `
[encoded data, 5840 characters]
`
