//go:build e2e
// +build e2e

package cases

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/message"
	"github.com/tidbcloud/observability/test/ft/utils"
)

// TestServerlessParentClusterLifecycle tests complete parent cluster lifecycle
func TestServerlessParentClusterLifecycle(t *testing.T) {
	t.Log("Test: Serverless Parent Cluster Lifecycle")

	t.Log("Test: Create Serverless Parent Cluster")
	// Create parent cluster
	req := utils.DefaultCreateServerlessParentReq(
		fmt.Sprintf("lc-parent-%d", time.Now().Unix()%1000000),
		"lc-project",
		"lc-app",
	)

	resp := utils.CreateCluster(req)
	require.NotEmpty(t, resp.ID)
	defer utils.DeleteServerlessCluster(resp.ID)

	// Verify cluster creation via API
	t.Log("Test: Get Serverless Parent Cluster via API")
	exists, cluster := utils.GetServerlessClusterIfExisted(resp.ID)
	if !exists {
		time.Sleep(5 * time.Second)
		exists, cluster = utils.GetServerlessClusterIfExisted(resp.ID)
		require.True(t, exists)
	}
	require.NotNil(t, cluster)
	assert.Equal(t, req.TenantID, cluster.TenantID)
	assert.Equal(t, req.ProjectID, cluster.ProjectID)
	assert.Equal(t, req.ApplicationID, cluster.ApplicationID)

	var err error
	// Verify cluster exists in database
	// err := utils.CheckClusterInDatabase(t, resp.ID)
	// assert.NoError(t, err, "Cluster should exist in database with all records")

	// Verify status transitions
	utils.CheckStatus(t, resp.ID, utils.GetServerlessCluster, constants.CREATING, constants.ACTIVE)

	// Verify metrics routes
	t.Log("Test: Verify Internal Metrics Route")
	err = utils.CheckServerlessInternalMetricsRoute(t, cluster)
	assert.NoError(t, err, "Internal metrics route should be accessible")

	// Get cluster detail
	t.Log("Test: Get Serverless Cluster")
	detailCluster := utils.GetServerlessCluster(resp.ID)
	assert.Equal(t, resp.ID, detailCluster.ID)
	assert.NotEmpty(t, detailCluster.MonitorInfo.DNSName)

	// Delete cluster
	deleteResp := utils.DeleteServerlessCluster(resp.ID)
	assert.Equal(t, http.StatusAccepted, deleteResp.StatusCode)

	time.Sleep(15 * time.Second)

	// Verify cluster deleted via API
	t.Log("Test: Delete Serverless Cluster")
	exists, _ = utils.GetServerlessClusterIfExisted(resp.ID)
	assert.False(t, exists, "Cluster should be deleted")

	// Verify cluster deleted from database
	// err = utils.CheckClusterDeletedFromDatabase(t, resp.ID)
	// assert.NoError(t, err, "Cluster should be deleted from database")
}

// TestServerlessChildCluster tests serverless child cluster with parent
func TestServerlessChildCluster(t *testing.T) {
	t.Log("Test: Serverless Child Cluster")

	// Create parent cluster
	parentReq := utils.DefaultCreateServerlessParentReq(
		fmt.Sprintf("ft-parent-%d", time.Now().Unix()%1000000),
		"ft-project",
		"ft-app",
	)
	parentResp := utils.CreateCluster(parentReq)
	require.NotEmpty(t, parentResp.ID)
	defer utils.DeleteServerlessCluster(parentResp.ID)

	time.Sleep(10 * time.Second)

	// Create child cluster
	keyspaceID := fmt.Sprintf("%d", time.Now().Unix())
	childReq := utils.DefaultCreateServerlessChildReq(
		fmt.Sprintf("child-%d", time.Now().Unix()%1000000),
		"child-project",
		"child-app",
		parentResp.ID,
		keyspaceID,
	)

	childResp := utils.CreateCluster(childReq)
	require.NotEmpty(t, childResp.ID)
	defer utils.DeleteServerlessCluster(childResp.ID)

	// Verify child cluster via API
	cluster := utils.GetServerlessCluster(childResp.ID)
	assert.Equal(t, childReq.TenantID, cluster.TenantID)
	assert.Equal(t, constants.SHARED, cluster.ShareMode)

	var err error
	// Verify child cluster in database
	// err := utils.CheckClusterInDatabase(t, childResp.ID)
	// assert.NoError(t, err, "Child cluster should exist in database")

	time.Sleep(5 * time.Second)

	// Verify metrics access
	err = utils.CheckServerlessExternalMetricsRoute(t, cluster)
	assert.NoError(t, err)
}

// TestClusterQueryOperations tests cluster query and list operations
func TestServerlessClusterQueryOperations(t *testing.T) {
	t.Log("Test: Cluster Query Operations")

	tenantID := fmt.Sprintf("query-%d", time.Now().Unix()%1000000)

	// Create one parent cluster
	parentReq := utils.DefaultCreateServerlessParentReq(tenantID, "parent-project", "parent-app")
	parentResp := utils.CreateCluster(parentReq)
	defer utils.DeleteServerlessCluster(parentResp.ID)

	time.Sleep(10 * time.Second)

	child1Req := utils.DefaultCreateServerlessChildReq(
		tenantID,
		"project1",
		"app1",
		parentResp.ID,
		fmt.Sprintf("%d", time.Now().Unix()),
	)
	child1Resp := utils.CreateCluster(child1Req)
	defer utils.DeleteServerlessCluster(child1Resp.ID)

	child2Req := utils.DefaultCreateServerlessChildReq(
		tenantID,
		"project1",
		"app2",
		parentResp.ID,
		fmt.Sprintf("%d", time.Now().Unix()+1),
	)
	child2Resp := utils.CreateCluster(child2Req)
	defer utils.DeleteServerlessCluster(child2Resp.ID)

	child3Req := utils.DefaultCreateServerlessChildReq(
		tenantID,
		"project2",
		"app3",
		parentResp.ID,
		fmt.Sprintf("%d", time.Now().Unix()+2),
	)
	child3Resp := utils.CreateCluster(child3Req)
	defer utils.DeleteServerlessCluster(child3Resp.ID)

	time.Sleep(5 * time.Second)

	// Query by tenant and project
	queryResp, err := utils.QueryClusters(message.QueryClustersReq{
		BizType:   utils.ServerlessBizType,
		TenantID:  tenantID,
		ProjectID: "project1",
		ContinueRequest: message.ContinueRequest{
			Limit: 10,
		},
	})
	require.NoError(t, err)
	assert.GreaterOrEqual(t, len(queryResp.Clusters), 2, "Should find at least 2 child clusters in project1")

	// Verify indexed queries
	foundApp1, foundApp2 := false, false
	for _, cluster := range queryResp.Clusters {
		if cluster.ApplicationID == "app1" {
			foundApp1 = true
		}
		if cluster.ApplicationID == "app2" {
			foundApp2 = true
		}
	}
	assert.True(t, foundApp1 && foundApp2, "Should find both app1 and app2")

	// Query without filters (pagination test)
	paginationResult, err := utils.QueryClusters(message.QueryClustersReq{
		BizType:         utils.ServerlessBizType,
		TenantID:        tenantID,
		ContinueRequest: message.ContinueRequest{Limit: 5},
	})
	require.NoError(t, err)
	assert.LessOrEqual(t, len(paginationResult.Clusters), 5)

	// Test non-existent cluster
	exists, cluster := utils.GetServerlessClusterIfExisted("non-existent-id")
	assert.False(t, exists)
	assert.Nil(t, cluster)
}

// TestConcurrentClusterOperations tests concurrent operations
func TestServerlessConcurrentClusterOperations(t *testing.T) {
	t.Log("Test: Concurrent Cluster Operations with Unique Constraint")

	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()

	numAttempts := 3
	resultChan := make(chan struct {
		id   string
		code int
	}, numAttempts)

	// Use identical biztype, tenant, project, and application IDs
	tenant := "concurrent-tenant-1"
	projectID := "concurrent-project-1"
	appID := "concurrent-app-1"

	// Create clusters concurrently with identical metadata
	for i := 0; i < numAttempts; i++ {
		go func(index int) {
			req := utils.DefaultCreateServerlessParentReq(
				tenant,
				projectID,
				appID,
			)

			resp, err := http.DefaultClient.Do(utils.CreateReq(req))
			assert.NoError(t, err, "Failed to create cluster")
			defer resp.Body.Close()
			respContent, err := io.ReadAll(resp.Body)
			assert.NoError(t, err, "Failed to read response body")
			type Result struct {
				Code int                       `json:"code"`
				Data message.CreateClusterResp `json:"data"`
			}
			result := &Result{}
			assert.NoError(t, json.Unmarshal(respContent, result))
			t.Log("Created cluster with:", result, "code", resp.StatusCode, "and index:", index)
			output := struct {
				id   string
				code int
			}{}
			output.id = result.Data.ID
			output.code = resp.StatusCode

			resultChan <- output
		}(i)
	}

	// Wait for completion and collect results
	successfulIDs := make([]string, 0, numAttempts)
	failedCount := 0
	for i := 0; i < numAttempts; i++ {
		select {
		case result := <-resultChan:
			if result.code != http.StatusAccepted {
				failedCount++
			} else {
				successfulIDs = append(successfulIDs, result.id)
			}
		case <-ctx.Done():
			t.Fatal("Timeout waiting for concurrent creates")
		}
	}

	defer func() {
		for _, id := range successfulIDs {
			utils.DeleteServerlessCluster(id)
		}
	}()

	// Verify that only one cluster was created successfully due to unique constraint
	assert.Equal(t, 1, len(successfulIDs), "Expected exactly one cluster to be created successfully")
	assert.Equal(t, numAttempts-1, failedCount, "Expected exactly one cluster creation to fail")

	// Verify the successful cluster
	if len(successfulIDs) > 0 {
		cluster := utils.GetServerlessCluster(successfulIDs[0])
		assert.NotEmpty(t, cluster.ID)
		assert.Equal(t, tenant, cluster.TenantID)
		assert.Equal(t, projectID, cluster.ProjectID)
		assert.Equal(t, appID, cluster.ApplicationID)
	}
}
