package tikv

import (
	"context"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func newTestApi(t *testing.T, handler http.Handler) Api {
	t.Helper()
	ts := httptest.NewServer(handler)
	t.Cleanup(ts.Close)
	return NewApi(strings.TrimPrefix(ts.URL, "http://"), nil)
}

type recordedRequest struct {
	method string
	path   string
	query  url.Values
}

func newRecordingHandler(status int, body string) (http.Handler, *recordedRequest) {
	rec := &recordedRequest{}
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		rec.method = r.Method
		rec.path = r.URL.Path
		rec.query = r.URL.Query()
		w.WriteHeader(status)
		_, _ = w.Write([]byte(body))
	}), rec
}

func TestCreateRestorePitrQuery(t *testing.T) {
	handler, rec := newRecordingHandler(http.StatusOK, "")
	api := newTestApi(t, handler)

	err := api.CreateRestore(context.Background(), 123, 1, "ks-target", "ks-source",
		time.Date(2024, 8, 10, 0, 0, 0, 0, time.UTC))

	require.NoError(t, err)
	require.Equal(t, http.MethodPut, rec.method)
	require.Equal(t, "/api/v1/restore_keyspace/123", rec.path)
	require.Equal(t, url.Values{
		"cluster_id":      []string{"1"},
		"keyspace":        []string{"ks-target"},
		"source_keyspace": []string{"ks-source"},
		"point_in_time":   []string{"2024-08-10 00:00:00.000"},
	}, rec.query)
}

func TestCreateOnlineSnapshotRestoreQuery(t *testing.T) {
	handler, rec := newRecordingHandler(http.StatusOK, "")
	api := newTestApi(t, handler)

	err := api.CreateOnlineSnapshotRestore(context.Background(), 456, 2, "ks-target", "ks-source")

	require.NoError(t, err)
	require.Equal(t, http.MethodPut, rec.method)
	require.Equal(t, "/api/v1/restore_keyspace/456", rec.path)
	require.Equal(t, url.Values{
		"cluster_id":      []string{"2"},
		"keyspace":        []string{"ks-target"},
		"source_keyspace": []string{"ks-source"},
		"restore_mode":    []string{"online_snapshot"},
	}, rec.query)
	// the server rejects online_snapshot combined with any of these
	require.NotContains(t, rec.query, "point_in_time")
	require.NotContains(t, rec.query, "backup_id")
	require.NotContains(t, rec.query, "backup_name")
}

func TestCreateOnlineSnapshotRestoreConcurrencyLimit(t *testing.T) {
	handler, _ := newRecordingHandler(http.StatusInternalServerError, "restore reach concurrency limit")
	api := newTestApi(t, handler)

	err := api.CreateOnlineSnapshotRestore(context.Background(), 456, 2, "ks-target", "ks-source")

	require.ErrorIs(t, err, ErrRestoreConcurrencyLimit)
}

func TestGetRestoreProgressSucceed(t *testing.T) {
	handler, _ := newRecordingHandler(http.StatusOK,
		`{"status":"Succeed","restore_ts":123456,"restore_bytes":100}`)
	api := newTestApi(t, handler)

	progress, err := api.GetRestoreProgress(context.Background(), 7, 1, "ks-target")

	require.NoError(t, err)
	require.Equal(t, "Succeed", progress.Status)
	require.Equal(t, uint64(123456), progress.RestoreTs)
	require.Equal(t, uint64(100), progress.RestoreBytes)
}

func TestGetRestoreProgressNotExist(t *testing.T) {
	handler, _ := newRecordingHandler(http.StatusNotFound, "")
	api := newTestApi(t, handler)

	_, err := api.GetRestoreProgress(context.Background(), 7, 1, "ks-target")

	require.ErrorIs(t, err, ErrRestoreTaskNotExist)
}
