package commontasks

import (
	"context"
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	agentv2 "github.com/tidbcloud/observability/agent-control-server/api/v2"
	"github.com/tidbcloud/observability/agent-control-server/pkg/consts"
	"github.com/tidbcloud/observability/agent-control-server/pkg/deploy"
)

type MockGuardianAgent struct {
	metav1.TypeMeta
	metav1.ObjectMeta
	paused bool
	phase  string
}

func (m *MockGuardianAgent) DeepCopyObject() runtime.Object {
	return &MockGuardianAgent{
		TypeMeta:   m.TypeMeta,
		ObjectMeta: *m.ObjectMeta.DeepCopy(),
		paused:     m.paused,
		phase:      m.phase,
	}
}

func (m *MockGuardianAgent) Paused() bool {
	return m.paused
}

func (m *MockGuardianAgent) Phase() string {
	return m.phase
}

func (m *MockGuardianAgent) SetPhase(phase string) {
	m.phase = phase
}

var _ client.Object = &MockGuardianAgent{}

func TestGuardianTask_Create(t *testing.T) {
	tests := []struct {
		name          string
		initialPhase  string
		hasFinalizer  bool
		paused        bool
		wantPhase     string
		wantFinalizer bool
		wantResult    deploy.Result
	}{
		{
			name:          "add finalizer if missing",
			initialPhase:  "",
			hasFinalizer:  false,
			wantPhase:     "",
			wantFinalizer: true,
			wantResult:    deploy.Panic(nil).WithRequeueAfter(0),
		},
		{
			name:          "set creating phase if empty",
			initialPhase:  "",
			hasFinalizer:  true,
			wantPhase:     agentv2.O11yAgentPhaseCreating,
			wantFinalizer: true,
			wantResult:    deploy.Done(),
		},
		{
			name:          "no op if already creating",
			initialPhase:  agentv2.O11yAgentPhaseCreating,
			hasFinalizer:  true,
			wantPhase:     agentv2.O11yAgentPhaseCreating,
			wantFinalizer: true,
			wantResult:    deploy.Done(),
		},
		{
			name:          "wake up from hibernation",
			initialPhase:  agentv2.O11yAgentPhaseHibernated,
			hasFinalizer:  true,
			paused:        false,
			wantPhase:     agentv2.O11yAgentPhaseWakeUp,
			wantFinalizer: true,
			wantResult:    deploy.Panic(nil).WithRequeueAfter(0),
		},
		{
			name:          "stay hibernated if paused",
			initialPhase:  agentv2.O11yAgentPhaseHibernated,
			hasFinalizer:  true,
			paused:        true,
			wantPhase:     agentv2.O11yAgentPhaseHibernated,
			wantFinalizer: true,
			wantResult:    deploy.Done(),
		},
		{
			name:          "accept panic phase",
			initialPhase:  agentv2.O11yAgentPhasePanic,
			hasFinalizer:  true,
			wantPhase:     agentv2.O11yAgentPhasePanic,
			wantFinalizer: true,
			wantResult:    deploy.Done(),
		},
		{
			name:          "unexpected phase",
			initialPhase:  "UnknownPhase",
			hasFinalizer:  true,
			wantPhase:     "UnknownPhase",
			wantFinalizer: true,
			wantResult:    deploy.Panic(fmt.Errorf("unexpected o11y agent phase during creation: UnknownPhase")),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			agent := &MockGuardianAgent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-agent",
					Namespace: "default",
				},
				phase:  tt.initialPhase,
				paused: tt.paused,
			}
			if tt.hasFinalizer {
				controllerutil.AddFinalizer(agent, consts.AnnotationKeyO11yAgentFinalizer)
			}

			task := NewGuardianTask(agent, deploy.ClusterMeta{Agent: "test-agent"})
			got := task.Create(context.Background())

			assert.Equal(t, "Guardian", task.Name())
			assert.Equal(t, "test-agent", task.ClusterMeta().Agent)
			assert.Equal(t, tt.wantResult, got)
			assert.Equal(t, tt.wantPhase, agent.Phase())
			assert.Equal(t, tt.wantFinalizer, controllerutil.ContainsFinalizer(agent, consts.AnnotationKeyO11yAgentFinalizer))
		})
	}
}

func TestGuardianTask_Delete(t *testing.T) {
	tests := []struct {
		name         string
		initialPhase string
		wantPhase    string
		wantResult   deploy.Result
	}{
		{
			name:         "set pending delete from normal",
			initialPhase: agentv2.O11yAgentPhaseNormal,
			wantPhase:    agentv2.O11yAgentPhasePendingDelete,
			wantResult:   deploy.Panic(nil).WithRequeueAfter(0),
		},
		{
			name:         "set pending delete from empty",
			initialPhase: "",
			wantPhase:    agentv2.O11yAgentPhasePendingDelete,
			wantResult:   deploy.Panic(nil).WithRequeueAfter(0),
		},
		{
			name:         "transition pending delete to deleting",
			initialPhase: agentv2.O11yAgentPhasePendingDelete,
			wantPhase:    agentv2.O11yAgentPhaseDeleting,
			wantResult:   deploy.Done(),
		},
		{
			name:         "stay deleting",
			initialPhase: agentv2.O11yAgentPhaseDeleting,
			wantPhase:    agentv2.O11yAgentPhaseDeleting,
			wantResult:   deploy.Done(),
		},
		{
			name:         "set pending delete from panic",
			initialPhase: agentv2.O11yAgentPhasePanic,
			wantPhase:    agentv2.O11yAgentPhasePendingDelete,
			wantResult:   deploy.Panic(nil).WithRequeueAfter(0),
		},
		{
			name:         "unexpected phase",
			initialPhase: "UnknownPhase",
			wantPhase:    "UnknownPhase",
			wantResult:   deploy.Panic(fmt.Errorf("unexpected o11y agent phase during deletion: UnknownPhase")),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			agent := &MockGuardianAgent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-agent",
					Namespace: "default",
				},
				phase: tt.initialPhase,
			}

			task := NewGuardianTask(agent, deploy.ClusterMeta{})
			got := task.Delete(context.Background())

			assert.Equal(t, tt.wantResult, got)
			assert.Equal(t, tt.wantPhase, agent.Phase())
		})
	}
}
