package common

import (
	"context"
	"testing"

	"github.com/go-logr/logr"
	"github.com/go-logr/logr/testr"
	"github.com/stretchr/testify/assert"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	observabilitypingcapcomv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/deploy"
	"github.com/tidbcloud/observability/pkg/resourceworker/controllers/serverless/task"
)

func newTestLogger(t *testing.T) logr.Logger {
	return testr.New(t)
}

func newTestContext(t *testing.T, cluster *observabilitypingcapcomv1.Cluster) *task.Context {
	if cluster == nil {
		cluster = &observabilitypingcapcomv1.Cluster{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "test-cluster",
				Namespace: "default",
			},
		}
	}

	return &task.Context{
		Cluster: cluster,
		Logger:  newTestLogger(t),
	}
}

func TestNewGuardian(t *testing.T) {
	ctx := newTestContext(t, nil)
	guardian := NewGuardian(ctx)

	assert.NotNil(t, guardian)
	assert.NotNil(t, guardian.BaseTask)
	assert.NotNil(t, guardian.Context)
	assert.Equal(t, ctx.Cluster, guardian.Cluster)
}

func TestGuardian_Name(t *testing.T) {
	ctx := newTestContext(t, nil)
	guardian := NewGuardian(ctx)

	name := guardian.Name()
	assert.Equal(t, "Guardian", name)
}

func TestGuardian_Create_EmptyPhase(t *testing.T) {
	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-cluster",
			Namespace: "default",
		},
		Status: observabilitypingcapcomv1.ClusterStatus{
			Phase: "",
		},
	}

	ctx := newTestContext(t, cluster)
	guardian := NewGuardian(ctx)

	result := guardian.Create(context.Background())

	// Should set phase to CREATING
	assert.Equal(t, constants.CREATING, cluster.Status.Phase)
	assert.True(t, result.IsDoneState())
	assert.Nil(t, result.Error())
}

func TestGuardian_Create_CreatingPhase(t *testing.T) {
	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-cluster",
			Namespace: "default",
		},
		Status: observabilitypingcapcomv1.ClusterStatus{
			Phase: constants.CREATING,
		},
	}

	ctx := newTestContext(t, cluster)
	guardian := NewGuardian(ctx)

	result := guardian.Create(context.Background())

	// Phase should remain CREATING
	assert.Equal(t, constants.CREATING, cluster.Status.Phase)
	assert.True(t, result.IsDoneState())
	assert.Nil(t, result.Error())
}

func TestGuardian_Create_UpdatingPhase(t *testing.T) {
	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-cluster",
			Namespace: "default",
		},
		Status: observabilitypingcapcomv1.ClusterStatus{
			Phase: constants.UPDATING,
		},
	}

	ctx := newTestContext(t, cluster)
	guardian := NewGuardian(ctx)

	result := guardian.Create(context.Background())

	// Phase should remain UPDATING
	assert.Equal(t, constants.UPDATING, cluster.Status.Phase)
	assert.True(t, result.IsDoneState())
	assert.Nil(t, result.Error())
}

func TestGuardian_Create_ActivePhase(t *testing.T) {
	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-cluster",
			Namespace: "default",
		},
		Status: observabilitypingcapcomv1.ClusterStatus{
			Phase: constants.ACTIVE,
		},
	}

	ctx := newTestContext(t, cluster)
	guardian := NewGuardian(ctx)

	result := guardian.Create(context.Background())

	// Phase should remain ACTIVE
	assert.Equal(t, constants.ACTIVE, cluster.Status.Phase)
	assert.True(t, result.IsDoneState())
	assert.Nil(t, result.Error())
}

func TestGuardian_Create_UnexpectedPhase(t *testing.T) {
	testCases := []struct {
		name  string
		phase constants.ResourceStatus
	}{
		{
			name:  "DELETING phase",
			phase: constants.DELETING,
		},
		{
			name:  "PAUSED phase",
			phase: constants.PAUSED,
		},
		{
			name:  "TO_BE_DELETED phase",
			phase: constants.TO_BE_DELETED,
		},
		{
			name:  "CREATION_FAILED phase",
			phase: constants.CREATION_FAILED,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			cluster := &observabilitypingcapcomv1.Cluster{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-cluster",
					Namespace: "default",
				},
				Status: observabilitypingcapcomv1.ClusterStatus{
					Phase: tc.phase,
				},
			}

			ctx := newTestContext(t, cluster)
			guardian := NewGuardian(ctx)

			result := guardian.Create(context.Background())

			// Should return Panic with error
			assert.False(t, result.IsDoneState())
			err := result.Error()
			assert.NotNil(t, err)
			assert.Contains(t, err.Error(), "unexpected serverless cluster phase during creation")
			assert.Contains(t, err.Error(), string(tc.phase))
		})
	}
}

func TestGuardian_Delete_EmptyPhase(t *testing.T) {
	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-cluster",
			Namespace: "default",
		},
		Status: observabilitypingcapcomv1.ClusterStatus{
			Phase: "",
		},
	}

	ctx := newTestContext(t, cluster)
	guardian := NewGuardian(ctx)

	result := guardian.Delete(context.Background())

	// Should set phase to DELETING
	assert.Equal(t, constants.DELETING, cluster.Status.Phase)

	// Should return non-done result (Panic state)
	assert.False(t, result.IsDoneState())
}

func TestGuardian_Delete_CreatingPhase(t *testing.T) {
	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-cluster",
			Namespace: "default",
		},
		Status: observabilitypingcapcomv1.ClusterStatus{
			Phase: constants.CREATING,
		},
	}

	ctx := newTestContext(t, cluster)
	guardian := NewGuardian(ctx)

	result := guardian.Delete(context.Background())

	// Should set phase to DELETING
	assert.Equal(t, constants.DELETING, cluster.Status.Phase)
	assert.False(t, result.IsDoneState())
}

func TestGuardian_Delete_ActivePhase(t *testing.T) {
	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-cluster",
			Namespace: "default",
		},
		Status: observabilitypingcapcomv1.ClusterStatus{
			Phase: constants.ACTIVE,
		},
	}

	ctx := newTestContext(t, cluster)
	guardian := NewGuardian(ctx)

	result := guardian.Delete(context.Background())

	// Should set phase to DELETING
	assert.Equal(t, constants.DELETING, cluster.Status.Phase)
	assert.False(t, result.IsDoneState())
}

func TestGuardian_Delete_DeletingPhase(t *testing.T) {
	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-cluster",
			Namespace: "default",
		},
		Status: observabilitypingcapcomv1.ClusterStatus{
			Phase: constants.DELETING,
		},
	}

	ctx := newTestContext(t, cluster)
	guardian := NewGuardian(ctx)

	result := guardian.Delete(context.Background())

	// Phase should remain DELETING
	assert.Equal(t, constants.DELETING, cluster.Status.Phase)
	assert.True(t, result.IsDoneState())
	assert.Nil(t, result.Error())
}

func TestGuardian_Delete_UnexpectedPhase(t *testing.T) {
	testCases := []struct {
		name  string
		phase constants.ResourceStatus
	}{
		{
			name:  "UPDATING phase",
			phase: constants.UPDATING,
		},
		{
			name:  "PAUSED phase",
			phase: constants.PAUSED,
		},
		{
			name:  "TO_BE_DELETED phase",
			phase: constants.TO_BE_DELETED,
		},
		{
			name:  "CREATION_FAILED phase",
			phase: constants.CREATION_FAILED,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			cluster := &observabilitypingcapcomv1.Cluster{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-cluster",
					Namespace: "default",
				},
				Status: observabilitypingcapcomv1.ClusterStatus{
					Phase: tc.phase,
				},
			}

			ctx := newTestContext(t, cluster)
			guardian := NewGuardian(ctx)

			result := guardian.Delete(context.Background())

			// Should return Panic with error
			assert.False(t, result.IsDoneState())
			err := result.Error()
			assert.NotNil(t, err)
			assert.Contains(t, err.Error(), "unexpected serverless cluster phase during deletion")
			assert.Contains(t, err.Error(), string(tc.phase))
		})
	}
}

func TestGuardian_Create_AllValidPhases(t *testing.T) {
	validPhases := []constants.ResourceStatus{
		"",
		constants.CREATING,
		constants.UPDATING,
		constants.ACTIVE,
	}

	for _, phase := range validPhases {
		t.Run(string(phase), func(t *testing.T) {
			cluster := &observabilitypingcapcomv1.Cluster{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-cluster",
					Namespace: "default",
				},
				Status: observabilitypingcapcomv1.ClusterStatus{
					Phase: phase,
				},
			}

			ctx := newTestContext(t, cluster)
			guardian := NewGuardian(ctx)

			result := guardian.Create(context.Background())

			// All valid phases should result in Done
			assert.True(t, result.IsDoneState())
			assert.Nil(t, result.Error())

			// Empty phase should be set to CREATING
			if phase == "" {
				assert.Equal(t, constants.CREATING, cluster.Status.Phase)
			} else {
				assert.Equal(t, phase, cluster.Status.Phase)
			}
		})
	}
}

func TestGuardian_Delete_AllValidPhases(t *testing.T) {
	testCases := []struct {
		name          string
		initialPhase  constants.ResourceStatus
		shouldRequeue bool
		expectedPhase constants.ResourceStatus
		shouldBeDone  bool
	}{
		{
			name:          "Empty phase",
			initialPhase:  "",
			shouldRequeue: true,
			expectedPhase: constants.DELETING,
			shouldBeDone:  false,
		},
		{
			name:          "CREATING phase",
			initialPhase:  constants.CREATING,
			shouldRequeue: true,
			expectedPhase: constants.DELETING,
			shouldBeDone:  false,
		},
		{
			name:          "ACTIVE phase",
			initialPhase:  constants.ACTIVE,
			shouldRequeue: true,
			expectedPhase: constants.DELETING,
			shouldBeDone:  false,
		},
		{
			name:          "DELETING phase",
			initialPhase:  constants.DELETING,
			shouldRequeue: false,
			expectedPhase: constants.DELETING,
			shouldBeDone:  true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			cluster := &observabilitypingcapcomv1.Cluster{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-cluster",
					Namespace: "default",
				},
				Status: observabilitypingcapcomv1.ClusterStatus{
					Phase: tc.initialPhase,
				},
			}

			ctx := newTestContext(t, cluster)
			guardian := NewGuardian(ctx)

			result := guardian.Delete(context.Background())

			assert.Equal(t, tc.expectedPhase, cluster.Status.Phase)
			assert.Equal(t, tc.shouldBeDone, result.IsDoneState())

			if tc.shouldRequeue {
				// Verify result is not done (Panic state)
				assert.False(t, result.IsDoneState())
			}
		})
	}
}

func TestGuardian_ContextFields(t *testing.T) {
	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-cluster",
			Namespace: "test-namespace",
		},
	}

	ctx := newTestContext(t, cluster)
	guardian := NewGuardian(ctx)

	// Verify context fields are accessible
	assert.Equal(t, "test-cluster", guardian.Cluster.Name)
	assert.Equal(t, "test-namespace", guardian.Cluster.Namespace)
	assert.NotNil(t, guardian.Logger)
}

func TestGuardian_Integration_CreateToDelete(t *testing.T) {
	// Simulate a full lifecycle: Create -> Active -> Delete
	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-cluster",
			Namespace: "default",
		},
		Status: observabilitypingcapcomv1.ClusterStatus{
			Phase: "",
		},
	}

	ctx := newTestContext(t, cluster)
	guardian := NewGuardian(ctx)

	// Step 1: Create with empty phase - should set to CREATING
	result := guardian.Create(context.Background())
	assert.Equal(t, constants.CREATING, cluster.Status.Phase)
	assert.True(t, result.IsDoneState())

	// Step 2: Create when already CREATING - should remain CREATING
	result = guardian.Create(context.Background())
	assert.Equal(t, constants.CREATING, cluster.Status.Phase)
	assert.True(t, result.IsDoneState())

	// Step 3: Simulate cluster becomes ACTIVE
	cluster.Status.Phase = constants.ACTIVE
	result = guardian.Create(context.Background())
	assert.True(t, result.IsDoneState())

	// Step 4: Delete ACTIVE cluster - should set to DELETING and requeue
	result = guardian.Delete(context.Background())
	assert.Equal(t, constants.DELETING, cluster.Status.Phase)
	assert.False(t, result.IsDoneState())

	// Step 5: Delete again when already DELETING - should return Done
	result = guardian.Delete(context.Background())
	assert.Equal(t, constants.DELETING, cluster.Status.Phase)
	assert.True(t, result.IsDoneState())
}

func TestGuardian_BaseTaskInterface(t *testing.T) {
	ctx := newTestContext(t, nil)
	guardian := NewGuardian(ctx)

	// Verify Guardian implements deploy.Task interface through BaseTask
	var _ deploy.Task = guardian.BaseTask

	// Test inherited methods from BaseTask
	assert.Equal(t, deploy.O11yTask, guardian.Category())

	postCreateResult := guardian.PostCreate(context.Background())
	assert.True(t, postCreateResult.IsDoneState())

	postDeleteResult := guardian.PostDelete(context.Background())
	assert.True(t, postDeleteResult.IsDoneState())

	metricsTime := guardian.MetricsStartTime()
	assert.Nil(t, metricsTime)
}
