package client_test

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net"
	"strings"
	"testing"
	"time"

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

func TestClient(t *testing.T) {
	listener, err := net.Listen("tcp", ":0")
	if err != nil {
		panic(err)
	}

	port := listener.Addr().(*net.TCPAddr).Port
	cfg := &gpaste.ServerConfig{
		LogLevel:      "ERROR",
		URL:           fmt.Sprintf("http://localhost:%d", port),
		SigningSecret: "TEST",
		Store:         &gpaste.ServerStoreConfig{Type: "memory"},
	}

	srv := api.NewHTTPServer(cfg)

	go func() {
		srv.Serve(listener)
	}()

	t.Cleanup(func() {
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		srv.Shutdown(ctx)
		listener.Close()
	})

	// Add users
	username := "admin"
	password := "admin"
	user := &users.User{
		Username: username,
		Role:     users.RoleAdmin,
	}
	if err := user.SetPassword(password); err != nil {
		t.Fatalf("Error setting password: %s", err)
	}
	if err := srv.Users.Store(user); err != nil {
		t.Fatalf("Error storing user: %s", err)
	}

	t.Run("Login", func(t *testing.T) {
		client := client.Client{BaseURL: cfg.URL}
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		if err := client.Login(ctx, username, password); err != nil {
			t.Fatalf("Error logging in: %s", err)
		}

		claims, err := srv.Auth.ValidateToken(client.AuthToken)
		if err != nil {
			t.Errorf("unable to get claims from token: %s", err)
		}

		if claims.Role != user.Role {
			t.Errorf("Claims have wrong role: %s", cmp.Diff(claims.Role, user.Role))
		}
		if claims.Subject != username {
			t.Errorf("Claims have wrong role: %s", cmp.Diff(claims.Subject, username))
		}

		t.Run("UserCreate", func(t *testing.T) {
			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
			defer cancel()
			username := "user"
			password := "user"

			if err := client.UserCreate(ctx, username, password); err != nil {
				t.Fatalf("Error creating user: %s", err)
			}

			user, err := srv.Users.Get(username)
			if err != nil {
				t.Fatalf("Error getting new user: %s", err)
			}

			if user.Username != username {
				t.Errorf("Username does not match.")
			}
			if err := user.ValidatePassword(password); err != nil {
				t.Errorf("Unable to validate password: %s", err)
			}
		})
	})
	t.Run("Upload", func(t *testing.T) {
		client := client.Client{BaseURL: cfg.URL}
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		fileContents := "this is the test file"
		fileBody := io.NopCloser(strings.NewReader(fileContents))
		file := &files.File{
			OriginalFilename: "filename.txt",
			Body:             fileBody,
		}

		resp, err := client.Upload(ctx, file)
		if err != nil {
			t.Fatalf("Error uploading: %s", err)
		}

		retrieved, err := srv.Files.Get(resp[0].ID)
		if err != nil {
			t.Fatalf("Error getting uploaded file from store: %s", err)
		}
		defer retrieved.Body.Close()

		buf := new(strings.Builder)
		if _, err := io.Copy(buf, retrieved.Body); err != nil {
			t.Fatalf("error reading body from store: %s", err)
		}
		if buf.String() != fileContents {
			t.Errorf("File contents does not match: %s", cmp.Diff(buf.String(), fileContents))
		}
	})
	t.Run("Download", func(t *testing.T) {
		client := client.Client{BaseURL: cfg.URL}
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		fileContents := "this is the test file"
		fileBody := io.NopCloser(strings.NewReader(fileContents))
		file := &files.File{
			ID:               uuid.NewString(),
			OriginalFilename: "filename.txt",
			Body:             fileBody,
		}

		if err := srv.Files.Store(file); err != nil {
			t.Fatalf("Error putting file in store: %s", err)
		}

		body, err := client.Download(ctx, file.ID)
		if err != nil {
			t.Fatalf("Error uploading: %s", err)
		}
		defer body.Close()

		buf := new(strings.Builder)
		if _, err := io.Copy(buf, body); err != nil {
			t.Fatalf("error reading body from store: %s", err)
		}
		if buf.String() != fileContents {
			t.Errorf("File contents does not match: %s", cmp.Diff(buf.String(), fileContents))
		}
	})
	t.Run("Save", func(t *testing.T) {
		c := client.Client{BaseURL: "http://example.org/gpaste", AuthToken: "tokenpls"}
		expectedConfig := "{\"base_url\":\"http://example.org/gpaste\",\"auth_token\":\"tokenpls\"}\n"
		buf := new(bytes.Buffer)
		err := c.WriteConfigToWriter(buf)
		if err != nil {
			t.Fatalf("Error writing config: %s", err)
		}

		if diff := cmp.Diff(buf.String(), expectedConfig); diff != "" {
			t.Errorf("Written config does not match expected: %s", diff)
		}
	})
	t.Run("Load", func(t *testing.T) {
		c := client.Client{}
		config := "{\"base_url\":\"http://pasta.example.org\",\"auth_token\":\"tokenpls\"}\n"
		expectedClient := client.Client{BaseURL: "http://pasta.example.org", AuthToken: "tokenpls"}
		sr := strings.NewReader(config)
		if err := c.LoadConfigFromReader(sr); err != nil {
			t.Fatalf("Error reading config: %s", err)
		}

		if diff := cmp.Diff(c, expectedClient, cmpopts.IgnoreUnexported(client.Client{})); diff != "" {
			t.Errorf("Client does not match expected: %s", diff)
		}

	})
}