//go:build integration
// +build integration

// can be executed with
// go test -v -tags integration -run TestGCSIntegration ./integration/...

package main

import (
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"path/filepath"
	"strings"
	"testing"

	"github.com/SAP/jenkins-library/pkg/gcs"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/testcontainers/testcontainers-go"
	"github.com/testcontainers/testcontainers-go/wait"
	"google.golang.org/api/option"
)

func TestGCSIntegrationClient(t *testing.T) {
	t.Parallel()
	ctx := context.Background()
	testdataPath, err := filepath.Abs("testdata/TestGCSIntegration")
	assert.NoError(t, err)

	req := testcontainers.GenericContainerRequest{
		ContainerRequest: testcontainers.ContainerRequest{
			AlwaysPullImage: true,
			Image:           "fsouza/fake-gcs-server:1.30.2",
			ExposedPorts:    []string{"4443/tcp"},
			WaitingFor:      wait.ForListeningPort("4443/tcp"),
			Cmd:             []string{"-scheme", "https", "-public-host", "localhost"},
			BindMounts: map[string]string{
				testdataPath: "/data",
			},
		},
		Started: true,
	}

	gcsContainer, err := testcontainers.GenericContainer(ctx, req)
	require.NoError(t, err)
	defer gcsContainer.Terminate(ctx)

	ip, err := gcsContainer.Host(ctx)
	require.NoError(t, err)
	port, err := gcsContainer.MappedPort(ctx, "4443")
	endpoint := fmt.Sprintf("https://%s:%s/storage/v1/", ip, port.Port())
	httpclient := http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
		},
	}

	t.Run("Test list files - success", func(t *testing.T) {
		bucketID := "sample-bucket"
		gcsClient, err := gcs.NewClient(gcs.WithClientOptions(option.WithEndpoint(endpoint), option.WithoutAuthentication(), option.WithHTTPClient(&httpclient)))
		assert.NoError(t, err)
		fileNames, err := gcsClient.ListFiles(bucketID)
		assert.NoError(t, err)
		assert.Equal(t, []string{"dir/test_file2.yaml", "test_file.txt"}, fileNames)
		err = gcsClient.Close()
		assert.NoError(t, err)
	})

	t.Run("Test list files in missing bucket", func(t *testing.T) {
		bucketID := "missing-bucket"
		gcsClient, err := gcs.NewClient(gcs.WithClientOptions(option.WithEndpoint(endpoint), option.WithoutAuthentication(), option.WithHTTPClient(&httpclient)))
		defer gcsClient.Close()
		assert.NoError(t, err)
		_, err = gcsClient.ListFiles(bucketID)
		assert.Error(t, err, "bucket doesn't exist")
		err = gcsClient.Close()
		assert.NoError(t, err)
	})

	t.Run("Test upload & download files - success", func(t *testing.T) {
		bucketID := "upload-bucket"
		file1Reader, file1Writer := io.Pipe()
		file2Reader, file2Writer := io.Pipe()
		gcsClient, err := gcs.NewClient(gcs.WithOpenFileFunction(openFileMock), gcs.WithCreateFileFunction(getCreateFileMock(file1Writer, file2Writer)),
			gcs.WithClientOptions(option.WithEndpoint(endpoint), option.WithoutAuthentication(), option.WithHTTPClient(&httpclient)))
		assert.NoError(t, err)
		err = gcsClient.UploadFile(bucketID, "file1", "test/file1")
		assert.NoError(t, err)
		err = gcsClient.UploadFile(bucketID, "folder/file2", "test/folder/file2")
		assert.NoError(t, err)
		fileNames, err := gcsClient.ListFiles(bucketID)
		assert.NoError(t, err)
		assert.Equal(t, []string{"placeholder", "test/file1", "test/folder/file2"}, fileNames)
		go gcsClient.DownloadFile(bucketID, "test/file1", "file1")
		fileContent, err := ioutil.ReadAll(file1Reader)
		assert.NoError(t, err)
		assert.Equal(t, file1Content, string(fileContent))
		go gcsClient.DownloadFile(bucketID, "test/folder/file2", "file2")
		fileContent, err = ioutil.ReadAll(file2Reader)
		assert.NoError(t, err)
		assert.Equal(t, file2Content, string(fileContent))

		err = gcsClient.Close()
		assert.NoError(t, err)
	})

	t.Run("Test upload missing file", func(t *testing.T) {
		bucketID := "upload-bucket"
		gcsClient, err := gcs.NewClient(gcs.WithOpenFileFunction(openFileMock),
			gcs.WithClientOptions(option.WithEndpoint(endpoint), option.WithoutAuthentication(), option.WithHTTPClient(&httpclient)))
		assert.NoError(t, err)
		err = gcsClient.UploadFile(bucketID, "file3", "test/file3")
		assert.Contains(t, err.Error(), "could not open source file")
		err = gcsClient.Close()
		assert.NoError(t, err)
	})

	t.Run("Test download missing file", func(t *testing.T) {
		bucketID := "upload-bucket"
		gcsClient, err := gcs.NewClient(gcs.WithOpenFileFunction(openFileMock),
			gcs.WithClientOptions(option.WithEndpoint(endpoint), option.WithoutAuthentication(), option.WithHTTPClient(&httpclient)))
		assert.NoError(t, err)
		err = gcsClient.DownloadFile(bucketID, "test/file3", "file3")
		assert.Contains(t, err.Error(), "could not open source file")
		err = gcsClient.Close()
		assert.NoError(t, err)
	})

	t.Run("Test download file - failed file creation", func(t *testing.T) {
		bucketID := "upload-bucket"
		_, file1Writer := io.Pipe()
		_, file2Writer := io.Pipe()
		gcsClient, err := gcs.NewClient(gcs.WithOpenFileFunction(openFileMock), gcs.WithCreateFileFunction(getCreateFileMock(file1Writer, file2Writer)),
			gcs.WithClientOptions(option.WithEndpoint(endpoint), option.WithoutAuthentication(), option.WithHTTPClient(&httpclient)))
		assert.NoError(t, err)
		err = gcsClient.DownloadFile(bucketID, "placeholder", "file3")
		assert.Contains(t, err.Error(), "could not create target file")
		err = gcsClient.Close()
		assert.NoError(t, err)
	})
}

const (
	file1Content = `test file`
	file2Content = `
		foo : bar
		pleh : help
		stuff : {'foo': 'bar', 'bar': 'foo'}
	`
)

func openFileMock(name string) (io.ReadCloser, error) {
	var fileContent string
	switch name {
	case "file1":
		fileContent = file1Content
	case "folder/file2":
		fileContent = file2Content
	default:
		return nil, errors.New("open file faled")
	}
	return ioutil.NopCloser(strings.NewReader(fileContent)), nil
}

func getCreateFileMock(file1Writer io.WriteCloser, file2Writer io.WriteCloser) func(name string) (io.WriteCloser, error) {
	return func(name string) (io.WriteCloser, error) {
		switch name {
		case "file1":
			return file1Writer, nil
		case "file2":
			return file2Writer, nil
		default:
			return nil, errors.New("could not create target file")
		}
	}
}
