package serverlessdb_operator

import (
	"fmt"
	"math/rand"
	"sync"
	"time"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

	. "gitcode.com/openFuyao/e2e-auto-test/e2e/serverlessdb-operator/utils"
)

func extractConnString(obj *unstructured.Unstructured) (string, bool, error) {
	return unstructured.NestedString(obj.Object, "status", "connectionString")
}

func extractPodName(obj *unstructured.Unstructured) (string, bool, error) {
	return unstructured.NestedString(obj.Object, "status", "podName")
}

var _ = Describe("ServerlessDB 并发申请",
	Label("serverlessdb-operator", "with-workload-cluster", "26.01"), Label("P0"), func() {

		It("serverlessdb-IT-014:并发申请实例,全部成功且 endpoint 互不相同", func() {
			createPoolAndAwaitWarm("pool-it014", 5)

			By("启动 5 个 goroutine 并发申请")
			const n = 5
			const maxRetries = 8
			apps := make([]string, n)
			for i := 0; i < n; i++ {
				apps[i] = fmt.Sprintf("app-it014-%d", i)
			}

			type result struct {
				appRef string
				resp   *InstanceResponse
				status int
				err    error
			}
			results := make([]result, n)
			var wg sync.WaitGroup
			wg.Add(n)
			for i := 0; i < n; i++ {
				go func(idx int) {
					defer wg.Done()
					time.Sleep(time.Duration(800+rand.Intn(3200)) * time.Millisecond)
					for attempt := 0; attempt < maxRetries; attempt++ {
						resp, status, err := apiClient.Allocate(ctx, testNs, &CreateInstanceRequest{
							AppRef:    apps[idx],
							PgVersion: pgVersion,
							Resources: InstanceResources{Cpu: allocateCpu, Memory: allocateMemory},
						})
						if err == nil && status == 200 {
							results[idx] = result{appRef: apps[idx], resp: resp, status: 200}
							return
						}
						if err == nil && status == 409 {
							list, lerr := ListDBInstances(ctx, k8sClient, testNs,
								fmt.Sprintf("%s=%s", LabelAppRef, apps[idx]))
							if lerr == nil && len(list.Items) > 0 {
								cs, _, _ := extractConnString(&list.Items[0])
								pn, _, _ := extractPodName(&list.Items[0])
								results[idx] = result{
									appRef: apps[idx],
									resp:   &InstanceResponse{Endpoint: cs, PodName: pn},
									status: 200,
								}
								return
							}
						}
						if attempt < maxRetries-1 {
							time.Sleep(5 * time.Second)
						}
					}
					results[idx] = result{appRef: apps[idx], err: fmt.Errorf("exhausted %d retries for %s", maxRetries, apps[idx])}
				}(i)
			}
			wg.Wait()

			By("校验全部返回 200")
			endpoints := make(map[string]struct{}, n)
			for i := 0; i < n; i++ {
				Expect(results[i].err).NotTo(HaveOccurred(), "app %s 申请不应出错", results[i].appRef)
				Expect(results[i].status).To(Equal(200), "app %s 应当返回 200", results[i].appRef)
				Expect(results[i].resp.Endpoint).NotTo(BeEmpty(), "app %s endpoint 应当非空", results[i].appRef)
				endpoints[results[i].resp.Endpoint] = struct{}{}
			}
			Expect(endpoints).To(HaveLen(n), "5 个 endpoint 应当互不相同")

			By("校验 5 个 DBInstance 均进入 Running")
			for i := 0; i < n; i++ {
				awaitInstancePhase(apps[i], "Running")
				DeferCleanup(func(appRef string) func() {
					return func() { _, _ = apiClient.Release(ctx, testNs, appRef) }
				}(apps[i]))
			}
		})
	})