package uploader

import (
	"crypto/rand"
	"fmt"
	"io"
	"mime/multipart"
	"os"
	"testing"

	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
)

func generateRandomFile(size int64) (string, error) {
	file, err := os.CreateTemp("", "data")
	if err != nil {
		return "", err
	}

	_, err = io.CopyN(file, rand.Reader, size)
	if err != nil {
		return "", err
	}

	return file.Name(), file.Close()
}

func BenchmarkAll(b *testing.B) {
	fileName, err := generateRandomFile(1024 * 1024 * 256)
	require.NoError(b, err)
	fmt.Println(fileName)
	defer os.Remove(fileName)

	b.Run("bare", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			err := bareRead(fileName)
			require.NoError(b, err)
		}
	})

	b.Run("default", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			err := defaultMultipart(fileName)
			require.NoError(b, err)
		}
	})

	b.Run("custom", func(b *testing.B) {
		for i := 0; i < b.N; i++ {
			err := customMultipart(fileName)
			require.NoError(b, err)
		}
	})
}

func defaultMultipart(filename string) error {
	r, bound := multipartFile(filename)

	logger, err := zap.NewProduction()
	if err != nil {
		return err
	}

	file, err := fetchMultipartFileDefault(logger, r, bound)
	if err != nil {
		return err
	}

	_, err = io.Copy(io.Discard, file)
	return err
}

func TestName(t *testing.T) {
	fileName, err := generateRandomFile(1024 * 1024 * 256)
	require.NoError(t, err)
	fmt.Println(fileName)
	defer os.Remove(fileName)

	err = defaultMultipart(fileName)
	require.NoError(t, err)
}

func customMultipart(filename string) error {
	r, bound := multipartFile(filename)

	logger, err := zap.NewProduction()
	if err != nil {
		return err
	}

	file, err := fetchMultipartFile(logger, r, bound)
	if err != nil {
		return err
	}

	_, err = io.Copy(io.Discard, file)
	return err
}

func fetchMultipartFileDefault(l *zap.Logger, r io.Reader, boundary string) (MultipartFile, error) {
	reader := multipart.NewReader(r, boundary)

	for {
		part, err := reader.NextPart()
		if err != nil {
			return nil, err
		}

		name := part.FormName()
		if name == "" {
			l.Debug("ignore part, empty form name")
			continue
		}

		filename := part.FileName()

		// ignore multipart/form-data values
		if filename == "" {
			l.Debug("ignore part, empty filename", zap.String("form", name))

			continue
		}

		return part, nil
	}
}

func bareRead(filename string) error {
	r, _ := multipartFile(filename)

	_, err := io.Copy(io.Discard, r)
	return err
}

func multipartFile(filename string) (*io.PipeReader, string) {
	r, w := io.Pipe()
	m := multipart.NewWriter(w)
	go func() {
		defer w.Close()
		defer m.Close()
		part, err := m.CreateFormFile("myFile", "foo.txt")
		if err != nil {
			fmt.Println(err)
			return
		}

		file, err := os.Open(filename)
		if err != nil {
			fmt.Println(err)
			return
		}
		defer file.Close()
		if _, err = io.Copy(part, file); err != nil {
			fmt.Println(err)
			return
		}
	}()

	return r, m.Boundary()
}