package files_test

import (
	"bytes"
	"io"
	"strings"
	"testing"
	"time"

	"git.t-juice.club/torjus/gpaste/files"
	"github.com/google/go-cmp/cmp"
	"github.com/google/uuid"
)

func RunFilestoreTest(s files.FileStore, t *testing.T) {
	t.Run("Basic", func(t *testing.T) {
		// Create
		dataString := "TEST_LOL_OMG"
		id := uuid.Must(uuid.NewRandom()).String()
		bodyBuf := &bytes.Buffer{}
		bodyBuf.Write([]byte(dataString))
		body := io.NopCloser(bodyBuf)
		f := &files.File{
			ID:        id,
			MaxViews:  99,
			Body:      body,
			ExpiresOn: time.Now().Add(99 * time.Second),
		}

		err := s.Store(f)
		if err != nil {
			t.Fatalf("Unable to store file: %s", err)
		}

		// Retrieve
		retrieved, err := s.Get(id)
		if err != nil {
			t.Fatalf("Unable to retrieve file: %s", err)
		}

		retrievedBuf := &bytes.Buffer{}
		_, err = retrievedBuf.ReadFrom(retrieved.Body)
		if err != nil {
			t.Fatalf("Unable to read retrieved body: %s", err)
		}
		_ = retrieved.Body.Close()
		if err != nil {
			t.Fatalf("Error reading from retrieved file: %s", err)
		}

		if retrievedBuf.String() != dataString {
			t.Fatalf("Data from retrieved body mismatch. Got %s want %s", retrievedBuf.String(), dataString)
		}
		expected := &files.File{
			ID:        f.ID,
			MaxViews:  f.MaxViews,
			ExpiresOn: f.ExpiresOn,
			FileSize:  int64(len(dataString)),
		}

		ignoreBody := cmp.FilterPath(func(p cmp.Path) bool { return p.String() == "Body" }, cmp.Ignore())
		if diff := cmp.Diff(retrieved, expected, ignoreBody); diff != "" {
			t.Errorf("File comparison failed: %s", diff)
		}

		// List
		ids, err := s.List()
		if err != nil {
			t.Fatalf("Error doing list: %s", err)
		}
		if len(ids) != 1 {
			t.Fatalf("List has wrong length: %d", len(ids))
		}
		if ids[0] != id {
			t.Fatalf("ID is wrong. Got %s want %s", ids[0], id)
		}

		// Delete
		if err := s.Delete(id); err != nil {
			t.Fatalf("Error deleting file: %s", err)
		}

		ids, err = s.List()
		if err != nil {
			t.Fatalf("Error listing after delete: %s", err)
		}
		if len(ids) != 0 {
			t.Fatalf("List after delete has wrong length: %d", len(ids))
		}
	})
}

func RunPersistentFilestoreTest(newStoreFunc func() files.FileStore, t *testing.T) {
	s := newStoreFunc()

	files := []struct {
		File         *files.File
		ExpectedData string
	}{
		{
			File: &files.File{
				ID:               uuid.NewString(),
				OriginalFilename: "testfile.txt",
				MaxViews:         5,
				ExpiresOn:        time.Now().Add(10 * time.Minute),
				Body:             io.NopCloser(strings.NewReader("cocks!")),
				FileSize:         6,
			},
			ExpectedData: "cocks!",
		},
		{
			File: &files.File{
				ID:               uuid.NewString(),
				OriginalFilename: "testfile2.txt",
				MaxViews:         5,
				ExpiresOn:        time.Now().Add(10 * time.Minute),
				Body:             io.NopCloser(strings.NewReader("derps!")),
				FileSize:         6,
			},
			ExpectedData: "derps!",
		},
	}

	for _, f := range files {
		err := s.Store(f.File)
		if err != nil {
			t.Fatalf("Error storing file: %s", err)
		}
	}
	for _, f := range files {
		retrieved, err := s.Get(f.File.ID)
		if err != nil {
			t.Fatalf("Unable to retrieve file: %s", err)
		}

		ignoreBody := cmp.FilterPath(func(p cmp.Path) bool { return p.String() == "Body" }, cmp.Ignore())
		if !cmp.Equal(retrieved, f.File, ignoreBody) {
			t.Errorf("Mismatch: %s", cmp.Diff(retrieved, f.File))
		}
		buf := new(strings.Builder)
		if _, err := io.Copy(buf, retrieved.Body); err != nil {
			t.Fatalf("Error reading from body: %s", err)
		}
		retrieved.Body.Close()
		if buf.String() != f.ExpectedData {
			t.Fatalf("Data does not match. %s", cmp.Diff(buf.String(), f.ExpectedData))
		}
	}

	// Reopen store, and fetch again
	s = newStoreFunc()
	for _, f := range files {
		retrieved, err := s.Get(f.File.ID)
		if err != nil {
			t.Fatalf("Unable to retrieve file: %s", err)
		}

		ignoreBody := cmp.FilterPath(func(p cmp.Path) bool { return p.String() == "Body" }, cmp.Ignore())
		if !cmp.Equal(retrieved, f.File, ignoreBody) {
			t.Errorf("Mismatch: %s", cmp.Diff(retrieved, f.File))
		}
		buf := new(strings.Builder)
		if _, err := io.Copy(buf, retrieved.Body); err != nil {
			t.Fatalf("Error reading from body: %s", err)
		}
		retrieved.Body.Close()
		if buf.String() != f.ExpectedData {
			t.Fatalf("Data does not match. %s", cmp.Diff(buf.String(), f.ExpectedData))
		}
	}
}