package byocfixture

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"slices"
	"sync"
	"testing"
	"time"

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

func TestCreate(t *testing.T) {
	t.Parallel()

	var createdBody map[string]any
	server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		writer.Header().Set("Content-Type", "application/json")
		switch request.URL.Path {
		case "/v1beta2/regions/aws-us-west-2/nodeSpecs":
			assert.Equal(t, "42", request.URL.Query().Get("organizationId"))
			assert.Equal(t, "NODE_SPEC_COMPONENT_TYPE_TIKV", request.URL.Query().Get("componentType"))
			assert.Equal(t, "100", request.URL.Query().Get("pageSize"))
			_, _ = writer.Write([]byte(`{
				"nodeSpecs":[
					{"cpu":"4","memoryGi":16,"storages":[{"name":"data","defaultSizeGi":"200"}]},
					{"cpu":"8","memoryGi":32,"isDefault":true,"storages":[{"name":"data","defaultSizeGi":"500"}]}
				],
				"nodeCountOptions":{"defaultNodeCount":"3"}
			}`))
		case "/v1beta2/regions/aws-us-west-2:planResourcePoolCidr":
			_, _ = writer.Write([]byte(`{"recommendationStatus":"AVAILABLE","recommendation":{"cidr":"10.24.0.0/16"}}`))
		case "/v1beta2/resourcePools":
			assert.Equal(t, http.MethodPost, request.Method)
			assert.NoError(t, json.NewDecoder(request.Body).Decode(&createdBody))
			_, _ = writer.Write([]byte(`{"resourcePoolId":"123","state":"CREATING"}`))
		case "/v1beta2/resourcePools/123":
			_, _ = writer.Write([]byte(`{"resourcePoolId":"123","state":"ACTIVE"}`))
		default:
			http.NotFound(writer, request)
		}
	}))
	t.Cleanup(server.Close)

	client, err := NewClient(server.URL+"/v1beta2", server.Client())
	require.NoError(t, err)
	client.SetPolling(time.Millisecond, time.Second)
	poolID, err := client.Create(t.Context(), CreateOptions{
		OrganizationID: "42",
		CloudProvider:  "aws",
		Region:         "us-west-2",
		DisplayName:    "sce-ng-byoc-fixture-run-1",
	})
	require.NoError(t, err)
	require.Equal(t, "123", poolID)
	require.Equal(t, "aws-us-west-2", createdBody["regionId"])
	require.Equal(t, "10.24.0.0/16", createdBody["vpcCidr"])
	require.Equal(t, map[string]any{"unlimited": true}, createdBody["vcpuLimit"])
	capacity, ok := createdBody["capacity"].(map[string]any)
	require.True(t, ok)
	tikv, ok := capacity["tikv"].(map[string]any)
	require.True(t, ok)
	require.EqualValues(t, 3, tikv["nodeCount"])
	nodeSpec, ok := tikv["nodeSpec"].(map[string]any)
	require.True(t, ok)
	require.EqualValues(t, 8, nodeSpec["cpu"])
	require.EqualValues(t, 32, nodeSpec["memoryGi"])
}

func TestCleanupDeletesChildrenBeforePool(t *testing.T) {
	t.Parallel()

	var mutex sync.Mutex
	remaining := []string{"11", "12"}
	poolDeleted := false
	server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		mutex.Lock()
		defer mutex.Unlock()
		writer.Header().Set("Content-Type", "application/json")
		switch {
		case request.Method == http.MethodGet && request.URL.Path == "/v1beta2/tidbs":
			tidbs := make([]map[string]string, 0, len(remaining))
			for _, id := range remaining {
				tidbs = append(tidbs, map[string]string{"tidbId": id})
			}
			assert.NoError(t, json.NewEncoder(writer).Encode(map[string]any{"tidbs": tidbs}))
		case request.Method == http.MethodDelete && request.URL.Path == "/v1beta2/tidbs/11":
			remaining = slices.DeleteFunc(remaining, func(id string) bool { return id == "11" })
			_, _ = writer.Write([]byte(`{}`))
		case request.Method == http.MethodDelete && request.URL.Path == "/v1beta2/tidbs/12":
			remaining = slices.DeleteFunc(remaining, func(id string) bool { return id == "12" })
			_, _ = writer.Write([]byte(`{}`))
		case request.Method == http.MethodDelete && request.URL.Path == "/v1beta2/resourcePools/123":
			assert.Empty(t, remaining)
			poolDeleted = true
			_, _ = writer.Write([]byte(`{}`))
		case request.Method == http.MethodGet && request.URL.Path == "/v1beta2/resourcePools/123" && poolDeleted:
			http.NotFound(writer, request)
		default:
			http.NotFound(writer, request)
		}
	}))
	t.Cleanup(server.Close)

	client, err := NewClient(server.URL+"/v1beta2", server.Client())
	require.NoError(t, err)
	client.SetPolling(time.Millisecond, time.Second)
	require.NoError(t, client.Cleanup(context.Background(), "123"))
	require.True(t, poolDeleted)
}

func TestCreateCleansUpFailedPool(t *testing.T) {
	t.Parallel()

	deleted := false
	server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		writer.Header().Set("Content-Type", "application/json")
		switch {
		case request.URL.Path == "/v1beta2/regions/aws-us-west-2/nodeSpecs":
			_, _ = writer.Write(
				[]byte(
					`{"nodeSpecs":[{"cpu":"8","memoryGi":"32","storages":[{"name":"data","defaultSizeGi":"500"}]}],"nodeCountOptions":{"defaultNodeCount":"3"}}`,
				),
			)
		case request.URL.Path == "/v1beta2/regions/aws-us-west-2:planResourcePoolCidr":
			_, _ = writer.Write([]byte(`{"recommendationStatus":"AVAILABLE","recommendation":{"cidr":"10.24.0.0/16"}}`))
		case request.Method == http.MethodPost && request.URL.Path == "/v1beta2/resourcePools":
			_, _ = writer.Write([]byte(`{"resourcePoolId":"123","state":"CREATING"}`))
		case request.Method == http.MethodGet && request.URL.Path == "/v1beta2/resourcePools/123" && !deleted:
			_, _ = writer.Write([]byte(`{"resourcePoolId":"123","state":"FAILED"}`))
		case request.Method == http.MethodGet && request.URL.Path == "/v1beta2/tidbs":
			_, _ = writer.Write([]byte(`{"tidbs":[]}`))
		case request.Method == http.MethodDelete && request.URL.Path == "/v1beta2/resourcePools/123":
			deleted = true
			_, _ = writer.Write([]byte(`{}`))
		case request.Method == http.MethodGet && request.URL.Path == "/v1beta2/resourcePools/123" && deleted:
			http.NotFound(writer, request)
		default:
			http.NotFound(writer, request)
		}
	}))
	t.Cleanup(server.Close)

	client, err := NewClient(server.URL+"/v1beta2", server.Client())
	require.NoError(t, err)
	client.SetPolling(time.Millisecond, time.Second)
	_, err = client.Create(t.Context(), CreateOptions{
		OrganizationID: "42",
		CloudProvider:  "aws",
		Region:         "us-west-2",
		DisplayName:    "sce-ng-byoc-fixture-run-1",
	})
	require.ErrorContains(t, err, "entered FAILED state")
	require.True(t, deleted)
}
