package deploy

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/go-logr/logr"
	"github.com/go-logr/logr/testr"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	ctrl "sigs.k8s.io/controller-runtime"
)

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

// mockTask implements Task interface for testing
type mockTask struct {
	name             string
	category         TaskCategory
	createResult     Result
	postCreateResult Result
	deleteResult     Result
	postDeleteResult Result
	createCalled     bool
	postCreateCalled bool
	deleteCalled     bool
	postDeleteCalled bool
	metricsStartTime *time.Time
}

func newMockTask(name string) *mockTask {
	return &mockTask{
		name:             name,
		category:         O11yTask,
		createResult:     Done(),
		postCreateResult: Done(),
		deleteResult:     Done(),
		postDeleteResult: Done(),
	}
}

func (m *mockTask) Name() string {
	return m.name
}

func (m *mockTask) Category() TaskCategory {
	return m.category
}

func (m *mockTask) Create(ctx context.Context) Result {
	m.createCalled = true
	return m.createResult
}

func (m *mockTask) PostCreate(ctx context.Context) Result {
	m.postCreateCalled = true
	return m.postCreateResult
}

func (m *mockTask) Delete(ctx context.Context) Result {
	m.deleteCalled = true
	return m.deleteResult
}

func (m *mockTask) PostDelete(ctx context.Context) Result {
	m.postDeleteCalled = true
	return m.postDeleteResult
}

func (m *mockTask) MetricsStartTime() *time.Time {
	return m.metricsStartTime
}

func TestNewDeployer(t *testing.T) {
	logger := newMockLogger(t)
	task1 := newMockTask("task1")
	task2 := newMockTask("task2")
	tasks := []Task{task1, task2}
	ignoreDelete := []string{"task1"}

	deployer := NewDeployer(tasks, ignoreDelete, logger)

	assert.NotNil(t, deployer)
	assert.Equal(t, 2, len(deployer.tasks))
	assert.Equal(t, 1, len(deployer.ignoreDelete))
	assert.Equal(t, "task1", deployer.ignoreDelete[0])
	assert.Equal(t, 0, len(deployer.results))
}

func TestDeployer_Deploy_Success(t *testing.T) {
	logger := newMockLogger(t)
	task1 := newMockTask("task1")
	task2 := newMockTask("task2")
	tasks := []Task{task1, task2}

	deployer := NewDeployer(tasks, nil, logger)
	ctx := context.Background()

	results := deployer.Deploy(ctx)

	// Verify all tasks were executed
	assert.True(t, task1.createCalled)
	assert.True(t, task1.postCreateCalled)
	assert.True(t, task2.createCalled)
	assert.True(t, task2.postCreateCalled)

	// Verify results
	require.Equal(t, 4, len(results))
	assert.Equal(t, "task1", results[0].task)
	assert.Equal(t, stepCreate, results[0].stepType)
	assert.Equal(t, stateDone, results[0].state)

	assert.Equal(t, "task1", results[1].task)
	assert.Equal(t, stepPostCreate, results[1].stepType)

	assert.Equal(t, "task2", results[2].task)
	assert.Equal(t, stepCreate, results[2].stepType)

	assert.Equal(t, "task2", results[3].task)
	assert.Equal(t, stepPostCreate, results[3].stepType)
}

func TestDeployer_Deploy_WithError(t *testing.T) {
	logger := newMockLogger(t)
	task1 := newMockTask("task1")
	task1.createResult = Error(errors.New("create error"))

	task2 := newMockTask("task2")
	tasks := []Task{task1, task2}

	deployer := NewDeployer(tasks, nil, logger)
	ctx := context.Background()

	results := deployer.Deploy(ctx)

	// Task1 Create and PostCreate should be called
	assert.True(t, task1.createCalled)
	assert.True(t, task1.postCreateCalled)

	// Task2 should still be executed
	assert.True(t, task2.createCalled)
	assert.True(t, task2.postCreateCalled)

	// Verify results
	require.Equal(t, 4, len(results))
	assert.Equal(t, stateError, results[0].state)
	assert.NotNil(t, results[0].err)
}

func TestDeployer_Deploy_WithFatal(t *testing.T) {
	logger := newMockLogger(t)
	task1 := newMockTask("task1")
	task1.createResult = Fatal(errors.New("fatal error"))

	task2 := newMockTask("task2")
	tasks := []Task{task1, task2}

	deployer := NewDeployer(tasks, nil, logger)
	ctx := context.Background()

	results := deployer.Deploy(ctx)

	// Task1 Create should be called but NOT PostCreate
	assert.True(t, task1.createCalled)
	assert.False(t, task1.postCreateCalled)

	// Task2 should still be executed
	assert.True(t, task2.createCalled)

	// Verify results - should have 3 results (task1 create, task2 create, task2 postcreate)
	require.Equal(t, 3, len(results))
	assert.Equal(t, stateFatal, results[0].state)
}

func TestDeployer_Deploy_WithPanic(t *testing.T) {
	logger := newMockLogger(t)
	task1 := newMockTask("task1")
	task1.createResult = Panic(errors.New("panic error"))

	task2 := newMockTask("task2")
	tasks := []Task{task1, task2}

	deployer := NewDeployer(tasks, nil, logger)
	ctx := context.Background()

	results := deployer.Deploy(ctx)

	// Task1 Create should be called but NOT PostCreate
	assert.True(t, task1.createCalled)
	assert.False(t, task1.postCreateCalled)

	// Task2 should NOT be executed due to panic
	assert.False(t, task2.createCalled)
	assert.False(t, task2.postCreateCalled)

	// Verify results - should only have task1 create
	require.Equal(t, 1, len(results))
	assert.Equal(t, statePanic, results[0].state)
}

func TestDeployer_Destroy_Success(t *testing.T) {
	logger := newMockLogger(t)
	task1 := newMockTask("task1")
	task2 := newMockTask("task2")
	task3 := newMockTask("task3")
	tasks := []Task{task1, task2, task3}

	deployer := NewDeployer(tasks, nil, logger)
	ctx := context.Background()

	results := deployer.Destroy(ctx)

	// Guardian task (task1) should be executed first
	// Then tasks in reverse order: task3, task2, task1 again
	assert.True(t, task1.deleteCalled)
	assert.True(t, task1.postDeleteCalled)
	assert.True(t, task2.deleteCalled)
	assert.True(t, task2.postDeleteCalled)
	assert.True(t, task3.deleteCalled)
	assert.True(t, task3.postDeleteCalled)

	// Verify results count: guardian (task1) + reverse (task3, task2)
	// The guardian task (task1) is executed first, then task3, task2
	// Note: The code appends d.tasks[0] to the end, so we don't get task1 executed twice
	// We get: guardian execution, then reversed order of remaining tasks
	require.Equal(t, 6, len(results)) // 3 tasks * 2 steps
}

func TestDeployer_Destroy_WithIgnoreDelete(t *testing.T) {
	logger := newMockLogger(t)
	task1 := newMockTask("task1")
	task2 := newMockTask("task2")
	task3 := newMockTask("task3")
	tasks := []Task{task1, task2, task3}

	// Ignore task2 during deletion
	deployer := NewDeployer(tasks, []string{"task2"}, logger)
	ctx := context.Background()

	results := deployer.Destroy(ctx)

	// Task1 should be executed (guardian)
	assert.True(t, task1.deleteCalled)

	// Task2 should be skipped
	assert.False(t, task2.deleteCalled)
	assert.False(t, task2.postDeleteCalled)

	// Task3 should be executed
	assert.True(t, task3.deleteCalled)
	assert.True(t, task3.postDeleteCalled)

	// Verify task2 is not in results
	for _, r := range results {
		assert.NotEqual(t, "task2", r.task)
	}
}

func TestDeployer_Destroy_WithError(t *testing.T) {
	logger := newMockLogger(t)
	task1 := newMockTask("task1")
	task2 := newMockTask("task2")
	task2.deleteResult = Error(errors.New("delete error"))
	tasks := []Task{task1, task2}

	deployer := NewDeployer(tasks, nil, logger)
	ctx := context.Background()

	results := deployer.Destroy(ctx)

	// Both tasks should be executed
	assert.True(t, task1.deleteCalled)
	assert.True(t, task2.deleteCalled)
	assert.True(t, task2.postDeleteCalled) // PostDelete should still run

	// Find the error result
	var errorResult *Result
	for i := range results {
		if results[i].state == stateError {
			errorResult = &results[i]
			break
		}
	}
	require.NotNil(t, errorResult)
	assert.Equal(t, "task2", errorResult.task)
}

func TestDeployer_Destroy_WithFatal(t *testing.T) {
	logger := newMockLogger(t)
	task1 := newMockTask("task1")
	task2 := newMockTask("task2")
	task2.deleteResult = Fatal(errors.New("fatal error"))
	tasks := []Task{task1, task2}

	deployer := NewDeployer(tasks, nil, logger)
	ctx := context.Background()

	_ = deployer.Destroy(ctx)

	// Task2 Delete should be called but NOT PostDelete
	assert.True(t, task2.deleteCalled)
	assert.False(t, task2.postDeleteCalled)

	// Task1 should still be executed
	assert.True(t, task1.deleteCalled)
}

func TestDeployer_Destroy_WithPanic(t *testing.T) {
	logger := newMockLogger(t)
	task1 := newMockTask("task1")
	task2 := newMockTask("task2")
	task3 := newMockTask("task3")
	task2.deleteResult = Panic(errors.New("panic error"))
	tasks := []Task{task1, task2, task3}

	deployer := NewDeployer(tasks, nil, logger)
	ctx := context.Background()

	results := deployer.Destroy(ctx)

	// After append: [task1, task2, task3, task1]
	// Iterate [1:] in reverse: task1(last), task3, task2
	// So execution order: task1, task3, then task2 panics

	// All tasks should be executed at least once for Delete
	assert.True(t, task1.deleteCalled)
	assert.True(t, task3.deleteCalled)
	assert.True(t, task2.deleteCalled)

	// Task2 panics, so PostDelete should not be called
	assert.False(t, task2.postDeleteCalled)

	// Count task1 appearances (from the appended guardian)
	task1Count := 0
	for _, r := range results {
		if r.task == "task1" {
			task1Count++
		}
	}
	// Task1 from the appended position: delete + postdelete = 2
	assert.Equal(t, 2, task1Count)
}

func TestDeployer_Execute(t *testing.T) {
	logger := newMockLogger(t)
	deployer := NewDeployer(nil, nil, logger)
	ctx := context.Background()

	testErr := errors.New("test error")
	testFunc := func(ctx context.Context) Result {
		return Error(testErr)
	}

	result := deployer.execute(ctx, "testTask", testFunc, stepCreate)

	assert.Equal(t, "testTask", result.task)
	assert.Equal(t, stepCreate, result.stepType)
	assert.Equal(t, stateError, result.state)
	assert.Equal(t, testErr, result.err)

	// Verify result was added to deployer.results
	require.Equal(t, 1, len(deployer.results))
	assert.Equal(t, "testTask", deployer.results[0].task)
}

func TestDeployer_Execute_Done(t *testing.T) {
	logger := newMockLogger(t)
	deployer := NewDeployer(nil, nil, logger)
	ctx := context.Background()

	testFunc := func(ctx context.Context) Result {
		return Done()
	}

	result := deployer.execute(ctx, "testTask", testFunc, stepCreate)

	assert.Equal(t, stateDone, result.state)
	assert.Nil(t, result.err)
}

func TestDeployer_Execute_AllStepTypes(t *testing.T) {
	testCases := []struct {
		name     string
		stepType stepType
	}{
		{"Create", stepCreate},
		{"PostCreate", stepPostCreate},
		{"Delete", stepDelete},
		{"PostDelete", stepPostDelete},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			logger := newMockLogger(t)
			deployer := NewDeployer(nil, nil, logger)
			ctx := context.Background()

			testFunc := func(ctx context.Context) Result {
				return Done()
			}

			result := deployer.execute(ctx, "testTask", testFunc, tc.stepType)

			assert.Equal(t, tc.stepType, result.stepType)
			assert.Equal(t, stateDone, result.state)
		})
	}
}

func TestDeployer_Execute_AllStates(t *testing.T) {
	testCases := []struct {
		name   string
		result Result
		state  state
	}{
		{"Done", Done(), stateDone},
		{"Error", Error(errors.New("error")), stateError},
		{"Fatal", Fatal(errors.New("fatal")), stateFatal},
		{"Panic", Panic(errors.New("panic")), statePanic},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			logger := newMockLogger(t)
			deployer := NewDeployer(nil, nil, logger)
			ctx := context.Background()

			testFunc := func(ctx context.Context) Result {
				return tc.result
			}

			result := deployer.execute(ctx, "testTask", testFunc, stepCreate)

			assert.Equal(t, tc.state, result.state)
		})
	}
}

func TestDeployer_ComplexScenario(t *testing.T) {
	logger := newMockLogger(t)

	// Create tasks with various behaviors
	task1 := newMockTask("task1")
	task2 := newMockTask("task2")
	task2.createResult = Error(errors.New("task2 error"))

	task3 := newMockTask("task3")
	task3.postCreateResult = Error(errors.New("task3 post error"))

	tasks := []Task{task1, task2, task3}

	deployer := NewDeployer(tasks, nil, logger)
	ctx := context.Background()

	// Test Deploy
	deployResults := deployer.Deploy(ctx)

	// All tasks should be executed
	assert.True(t, task1.createCalled)
	assert.True(t, task2.createCalled)
	assert.True(t, task3.createCalled)

	// Verify deploy results count
	assert.Equal(t, 6, len(deployResults)) // 3 tasks * 2 steps each

	// Reset for destroy test
	deployer2 := NewDeployer(tasks, []string{"task2"}, logger)

	// Test Destroy with ignore
	destroyResults := deployer2.Destroy(ctx)

	// Task2 should be skipped
	assert.False(t, task2.deleteCalled)

	// Other tasks should be executed
	assert.True(t, task1.deleteCalled)
	assert.True(t, task3.deleteCalled)

	// Verify no task2 in results
	for _, r := range destroyResults {
		assert.NotEqual(t, "task2", r.task)
	}
}

func TestDeployer_EmptyTasks(t *testing.T) {
	logger := newMockLogger(t)
	deployer := NewDeployer([]Task{}, nil, logger)
	ctx := context.Background()

	deployResults := deployer.Deploy(ctx)
	assert.Equal(t, 0, len(deployResults))

	// Destroy with empty tasks will panic due to d.tasks[0] access
	// This is expected behavior based on the code design
	// The Destroy function expects at least a guardian task
}

func TestDeployer_SingleTask(t *testing.T) {
	logger := newMockLogger(t)
	task1 := newMockTask("task1")
	tasks := []Task{task1}

	deployer := NewDeployer(tasks, nil, logger)
	ctx := context.Background()

	// Deploy
	deployResults := deployer.Deploy(ctx)
	assert.Equal(t, 2, len(deployResults)) // create + postcreate

	// Reset task flags
	task1.createCalled = false
	task1.postCreateCalled = false

	// Destroy - task1 is both the guardian and the only task
	// After append: [task1, task1]
	// Iterate [1:] in reverse: task1
	deployer2 := NewDeployer([]Task{task1}, nil, logger)
	destroyResults := deployer2.Destroy(ctx)

	assert.True(t, task1.deleteCalled)
	// Only one execution: the appended task1
	assert.Equal(t, 2, len(destroyResults)) // delete + postdelete
}

func TestDeployer_IgnoreDeleteMultiple(t *testing.T) {
	logger := newMockLogger(t)
	task1 := newMockTask("task1")
	task2 := newMockTask("task2")
	task3 := newMockTask("task3")
	task4 := newMockTask("task4")
	tasks := []Task{task1, task2, task3, task4}

	deployer := NewDeployer(tasks, []string{"task2", "task4"}, logger)
	ctx := context.Background()

	results := deployer.Destroy(ctx)

	// Task2 and Task4 should be skipped
	assert.False(t, task2.deleteCalled)
	assert.False(t, task4.deleteCalled)

	// Task1 and Task3 should be executed
	assert.True(t, task1.deleteCalled)
	assert.True(t, task3.deleteCalled)

	// Verify results don't contain task2 or task4
	for _, r := range results {
		assert.NotEqual(t, "task2", r.task)
		assert.NotEqual(t, "task4", r.task)
	}
}

func TestDeployer_PostCreateNotCalledOnFatal(t *testing.T) {
	logger := newMockLogger(t)
	task := newMockTask("task1")
	task.createResult = Fatal(errors.New("fatal"))

	deployer := NewDeployer([]Task{task}, nil, logger)
	results := deployer.Deploy(context.Background())

	assert.True(t, task.createCalled)
	assert.False(t, task.postCreateCalled)
	assert.Equal(t, 1, len(results))
}

func TestDeployer_PostDeleteNotCalledOnFatal(t *testing.T) {
	logger := newMockLogger(t)
	task := newMockTask("task1")
	task.deleteResult = Fatal(errors.New("fatal"))

	deployer := NewDeployer([]Task{task}, nil, logger)
	deployer.Destroy(context.Background())

	assert.True(t, task.deleteCalled)
	assert.False(t, task.postDeleteCalled)
}

func TestDeployer_GuardianTaskBehavior(t *testing.T) {
	logger := newMockLogger(t)
	guardian := newMockTask("guardian")
	task1 := newMockTask("task1")
	task2 := newMockTask("task2")
	tasks := []Task{guardian, task1, task2}

	deployer := NewDeployer(tasks, nil, logger)
	results := deployer.Destroy(context.Background())

	// Guardian should appear first and last in execution
	// Original: [guardian, task1, task2]
	// After append: [guardian, task1, task2, guardian]
	// Iterate d.tasks[1:] in reverse: guardian(last), task2, task1
	assert.True(t, guardian.deleteCalled)
	assert.True(t, task1.deleteCalled)
	assert.True(t, task2.deleteCalled)

	// Count guardian appearances
	guardianCount := 0
	for _, r := range results {
		if r.task == "guardian" {
			guardianCount++
		}
	}
	// Guardian appears once in the reversed iteration (2 steps: delete + postdelete)
	assert.Equal(t, 2, guardianCount)
}

// Tests for task.go functions

func TestResult_Error(t *testing.T) {
	testCases := []struct {
		name     string
		result   Result
		hasError bool
	}{
		{
			name:     "Done state has no error",
			result:   Done(),
			hasError: false,
		},
		{
			name:     "Error state has error",
			result:   Error(errors.New("test error")),
			hasError: true,
		},
		{
			name:     "Fatal state has error",
			result:   Fatal(errors.New("fatal error")),
			hasError: true,
		},
		{
			name:     "Panic state has error",
			result:   Panic(errors.New("panic error")),
			hasError: true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			err := tc.result.Error()
			if tc.hasError {
				assert.NotNil(t, err)
			} else {
				assert.Nil(t, err)
			}
		})
	}
}

func TestResult_IsDoneState(t *testing.T) {
	assert.True(t, Done().IsDoneState())
	assert.False(t, Error(errors.New("error")).IsDoneState())
	assert.False(t, Fatal(errors.New("fatal")).IsDoneState())
	assert.False(t, Panic(errors.New("panic")).IsDoneState())
}

func TestPanicOrDone(t *testing.T) {
	// With nil error
	result := PanicOrDone(nil)
	assert.Equal(t, stateDone, result.state)
	assert.Nil(t, result.err)

	// With error
	testErr := errors.New("test error")
	result = PanicOrDone(testErr)
	assert.Equal(t, statePanic, result.state)
	assert.Equal(t, testErr, result.err)
}

func TestErrorOrDone(t *testing.T) {
	// With nil error
	result := ErrorOrDone(nil)
	assert.Equal(t, stateDone, result.state)
	assert.Nil(t, result.err)

	// With error
	testErr := errors.New("test error")
	result = ErrorOrDone(testErr)
	assert.Equal(t, stateError, result.state)
	assert.Equal(t, testErr, result.err)
}

func TestResult_WithRequeuAfter(t *testing.T) {
	result := Error(errors.New("test error"))
	duration := 5 * time.Second

	newResult := result.WithRequeueAfter(duration)

	assert.Equal(t, stateError, newResult.state)
	assert.NotNil(t, newResult.err)

	// Verify it's a retriable error
	requeue := ShouldRequeue(newResult.err)
	assert.True(t, requeue.Requeue)
	assert.Equal(t, duration, requeue.RequeueAfter)
}

func TestResult_WithName(t *testing.T) {
	result := Done()
	namedResult := result.WithName("test-task")

	assert.Equal(t, "test-task", namedResult.task)
	assert.Equal(t, stateDone, namedResult.state)
}

func TestMinRequeue(t *testing.T) {
	// Test with no errors
	results := []Result{
		Done().WithName("task1"),
		Done().WithName("task2"),
	}

	res, errs, retriableErrs := MinRequeue(results)
	assert.True(t, res.IsZero())
	assert.Nil(t, errs)
	assert.Nil(t, retriableErrs)

	// Test with regular errors
	results = []Result{
		Error(errors.New("error1")).WithName("task1"),
		Error(errors.New("error2")).WithName("task2"),
	}

	res, errs, retriableErrs = MinRequeue(results)
	assert.True(t, res.IsZero())
	assert.NotNil(t, errs)
	assert.Nil(t, retriableErrs)

	// Test with retriable errors
	results = []Result{
		Error(errors.New("error1")).WithName("task1").WithRequeueAfter(5 * time.Second),
		Error(errors.New("error2")).WithName("task2").WithRequeueAfter(10 * time.Second),
	}

	res, errs, retriableErrs = MinRequeue(results)
	assert.False(t, res.IsZero())
	assert.True(t, res.Requeue)
	assert.Equal(t, 5*time.Second, res.RequeueAfter) // Should be minimum
	assert.Nil(t, errs)
	assert.NotNil(t, retriableErrs)

	// Test with requeue only
	requeueOnlyErr := NewRetriableError(ErrorRequeueOnly, ctrl.Result{Requeue: true, RequeueAfter: 3 * time.Second})
	results = []Result{
		{state: stateError, err: requeueOnlyErr, task: "task1"},
	}

	res, errs, retriableErrs = MinRequeue(results)
	assert.False(t, res.IsZero())
	assert.Nil(t, errs)
	assert.Nil(t, retriableErrs) // RequeueOnly should not add to errors
}

func TestTaskWraper(t *testing.T) {
	task := newMockTask("wrapped-task")
	hookCalled := false
	var hookTask Task

	hook := func(ctx context.Context, t Task) {
		hookCalled = true
		hookTask = t
	}

	wrapper := NewTaskWraper(task, hook)
	assert.NotNil(t, wrapper)

	ctx := context.Background()

	// Test Create
	createResult := wrapper.Create(ctx)
	assert.Equal(t, stateDone, createResult.state)
	assert.True(t, task.createCalled)

	// Test PostCreate - hook should be called on success
	postResult := wrapper.PostCreate(ctx)
	assert.Equal(t, stateDone, postResult.state)
	assert.True(t, task.postCreateCalled)
	assert.True(t, hookCalled)
	assert.Equal(t, task, hookTask)
}

func TestTaskWraper_WithError(t *testing.T) {
	task := newMockTask("wrapped-task")
	task.createResult = Error(errors.New("create error"))

	hookCalled := false
	hook := func(ctx context.Context, t Task) {
		hookCalled = true
	}

	wrapper := NewTaskWraper(task, hook)
	ctx := context.Background()

	// Create with error
	wrapper.Create(ctx)

	// PostCreate - hook should NOT be called due to create error
	wrapper.PostCreate(ctx)
	assert.False(t, hookCalled)
}

func TestTaskWraper_WithPostCreateError(t *testing.T) {
	task := newMockTask("wrapped-task")
	task.postCreateResult = Error(errors.New("postcreate error"))

	hookCalled := false
	hook := func(ctx context.Context, t Task) {
		hookCalled = true
	}

	wrapper := NewTaskWraper(task, hook)
	ctx := context.Background()

	// Create succeeds
	wrapper.Create(ctx)

	// PostCreate has error - hook should NOT be called
	wrapper.PostCreate(ctx)
	assert.False(t, hookCalled)
}

func TestTaskWraper_WithRequeueOnly(t *testing.T) {
	task := newMockTask("wrapped-task")
	requeueErr := NewRetriableError(ErrorRequeueOnly, ctrl.Result{Requeue: true, RequeueAfter: 5 * time.Second})
	task.createResult = Result{state: stateError, err: requeueErr}

	hookCalled := false
	hook := func(ctx context.Context, t Task) {
		hookCalled = true
	}

	wrapper := NewTaskWraper(task, hook)
	ctx := context.Background()

	// Create with requeue-only error
	wrapper.Create(ctx)

	// PostCreate - hook SHOULD be called because RequeueOnly is not a real error
	wrapper.PostCreate(ctx)
	assert.True(t, hookCalled)
}

// Tests for retry.go functions

func TestNewRetriableError(t *testing.T) {
	testErr := errors.New("test error")
	result := ctrl.Result{Requeue: true, RequeueAfter: 5 * time.Second}

	retriable := NewRetriableError(testErr, result)

	assert.NotNil(t, retriable)
	assert.Equal(t, "test error", retriable.Error())
	assert.Equal(t, result, retriable.result)
}

func TestNewRetriableError_NilError(t *testing.T) {
	result := ctrl.Result{Requeue: true, RequeueAfter: 5 * time.Second}

	retriable := NewRetriableError(nil, result)

	assert.NotNil(t, retriable)
	assert.Equal(t, "", retriable.Error())
}

func TestNewRetriableError_Panic(t *testing.T) {
	assert.Panics(t, func() {
		NewRetriableError(errors.New("test"), ctrl.Result{})
	})
}

func TestNewRetriableError_Nested(t *testing.T) {
	testErr := errors.New("test error")
	result1 := ctrl.Result{Requeue: true, RequeueAfter: 5 * time.Second}
	result2 := ctrl.Result{Requeue: true, RequeueAfter: 10 * time.Second}

	retriable1 := NewRetriableError(testErr, result1)
	retriable2 := NewRetriableError(retriable1, result2)

	// Should return the same retriable with updated result
	assert.Equal(t, retriable1, retriable2)
	assert.Equal(t, result2, retriable2.result)
}

func TestShouldRequeue(t *testing.T) {
	// Non-retriable error
	regularErr := errors.New("regular error")
	result := ShouldRequeue(regularErr)
	assert.True(t, result.IsZero())

	// Retriable error
	retriableErr := NewRetriableError(errors.New("test"), ctrl.Result{Requeue: true, RequeueAfter: 5 * time.Second})
	result = ShouldRequeue(retriableErr)
	assert.False(t, result.IsZero())
	assert.True(t, result.Requeue)
	assert.Equal(t, 5*time.Second, result.RequeueAfter)
}

func TestRequeueOnly(t *testing.T) {
	// Regular error
	regularErr := errors.New("regular error")
	assert.False(t, RequeueOnly(regularErr))

	// Regular retriable error
	retriableErr := NewRetriableError(errors.New("test"), ctrl.Result{Requeue: true, RequeueAfter: 5 * time.Second})
	assert.False(t, RequeueOnly(retriableErr))

	// RequeueOnly error
	requeueOnlyErr := NewRetriableError(ErrorRequeueOnly, ctrl.Result{Requeue: true, RequeueAfter: 5 * time.Second})
	assert.True(t, RequeueOnly(requeueOnlyErr))
}

func TestMinResult(t *testing.T) {
	// Both not requeue
	lhs := ctrl.Result{}
	rhs := ctrl.Result{}
	result := minResult(lhs, rhs)
	assert.False(t, result.Requeue)

	// Only lhs requeues
	lhs = ctrl.Result{Requeue: true, RequeueAfter: 5 * time.Second}
	rhs = ctrl.Result{}
	result = minResult(lhs, rhs)
	assert.True(t, result.Requeue)
	assert.Equal(t, 5*time.Second, result.RequeueAfter)

	// Only rhs requeues
	lhs = ctrl.Result{}
	rhs = ctrl.Result{Requeue: true, RequeueAfter: 10 * time.Second}
	result = minResult(lhs, rhs)
	assert.True(t, result.Requeue)
	assert.Equal(t, 10*time.Second, result.RequeueAfter)

	// Both requeue, lhs is smaller
	lhs = ctrl.Result{Requeue: true, RequeueAfter: 5 * time.Second}
	rhs = ctrl.Result{Requeue: true, RequeueAfter: 10 * time.Second}
	result = minResult(lhs, rhs)
	assert.Equal(t, 5*time.Second, result.RequeueAfter)

	// Both requeue, rhs is smaller
	lhs = ctrl.Result{Requeue: true, RequeueAfter: 15 * time.Second}
	rhs = ctrl.Result{Requeue: true, RequeueAfter: 3 * time.Second}
	result = minResult(lhs, rhs)
	assert.Equal(t, 3*time.Second, result.RequeueAfter)

	// RequeueAfter set without Requeue flag
	lhs = ctrl.Result{RequeueAfter: 5 * time.Second}
	rhs = ctrl.Result{}
	result = minResult(lhs, rhs)
	assert.True(t, result.Requeue)
}

// Tests for scheme.go functions

func TestRemoteScheme(t *testing.T) {
	scheme, err := RemoteScheme()

	assert.NoError(t, err)
	assert.NotNil(t, scheme)

	// Verify that the scheme contains expected types from client-go
	// The scheme should have been initialized with clientgoscheme.AddToScheme
	assert.NotNil(t, scheme)
}
