package nodegroup

import (
	"testing"
	"time"

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

func TestNewStateMachine(t *testing.T) {
	sm := NewStateMachine("pool-123", "4c16g", StateNormal, 30*time.Minute)

	assert.Equal(t, StateNormal, sm.CurrentState)
	assert.Equal(t, "pool-123", sm.SharedPoolID)
	assert.Equal(t, "4c16g", sm.PrimarySpec)
	assert.Equal(t, 30*time.Minute, sm.CooldownDuration)
}

func TestNewStateMachineDefaultCooldown(t *testing.T) {
	sm := NewStateMachine("pool-123", "4c16g", StateNormal, 0)

	assert.Equal(t, 30*time.Minute, sm.CooldownDuration)
}

func TestStateMachine_CanTransition(t *testing.T) {
	tests := []struct {
		name     string
		state    State
		event    Event
		expected bool
	}{
		{"normal can detect shortage", StateNormal, EventShortageDetected, true},
		{"normal can be deprecated", StateNormal, EventDeprecationStarted, true},
		{"normal cannot create fallback", StateNormal, EventFallbackCreated, false},

		{"shortage can create fallback", StateShortageDetected, EventFallbackCreated, true},
		{"shortage can restore capacity", StateShortageDetected, EventCapacityRestored, true},
		{"shortage cannot be deprecated", StateShortageDetected, EventDeprecationStarted, false},

		{"fallback_creating can become ready", StateFallbackCreating, EventFallbackReady, true},
		{"fallback_creating cannot restore", StateFallbackCreating, EventCapacityRestored, false},

		{"fallback_ready can restore capacity", StateFallbackReady, EventCapacityRestored, true},
		{"fallback_ready cannot detect shortage again", StateFallbackReady, EventShortageDetected, false},

		{"recovering can cooldown", StateRecovering, EventCooldownExpired, true},
		{"recovering can detect shortage", StateRecovering, EventShortageDetected, true},

		{"draining can complete", StateDrainingFallback, EventDrainComplete, true},
		{"draining can detect shortage", StateDrainingFallback, EventShortageDetected, true},

		{"deleting can complete", StateDeletingFallback, EventFallbackDeleted, true},

		{"deprecated is terminal", StateDeprecated, EventCapacityRestored, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sm := NewStateMachine("pool-123", "4c16g", tt.state, 30*time.Minute)
			assert.Equal(t, tt.expected, sm.CanTransition(tt.event))
		})
	}
}

func TestStateMachine_Transition(t *testing.T) {
	tests := []struct {
		name        string
		fromState   State
		event       Event
		toState     State
		expectError bool
	}{
		{"normal to shortage", StateNormal, EventShortageDetected, StateShortageDetected, false},
		{"normal to deprecated", StateNormal, EventDeprecationStarted, StateDeprecated, false},
		{"shortage to creating", StateShortageDetected, EventFallbackCreated, StateFallbackCreating, false},
		{"shortage to normal", StateShortageDetected, EventCapacityRestored, StateNormal, false},
		{"creating to ready", StateFallbackCreating, EventFallbackReady, StateFallbackReady, false},
		{"ready to recovering", StateFallbackReady, EventCapacityRestored, StateRecovering, false},
		{"recovering to draining", StateRecovering, EventCooldownExpired, StateDrainingFallback, false},
		{"recovering to ready (shortage)", StateRecovering, EventShortageDetected, StateFallbackReady, false},
		{"draining to deleting", StateDrainingFallback, EventDrainComplete, StateDeletingFallback, false},
		{"draining to ready (shortage)", StateDrainingFallback, EventShortageDetected, StateFallbackReady, false},
		{"deleting to normal", StateDeletingFallback, EventFallbackDeleted, StateNormal, false},
		{"invalid transition", StateNormal, EventFallbackCreated, "", true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sm := NewStateMachine("pool-123", "4c16g", tt.fromState, 30*time.Minute)
			err := sm.Transition(tt.event)

			if tt.expectError {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
				assert.Equal(t, tt.toState, sm.CurrentState)
			}
		})
	}
}

func TestStateMachine_NextState(t *testing.T) {
	sm := NewStateMachine("pool-123", "4c16g", StateNormal, 30*time.Minute)

	nextState, err := sm.NextState(EventShortageDetected)
	require.NoError(t, err)
	assert.Equal(t, StateShortageDetected, nextState)
	assert.Equal(t, StateNormal, sm.CurrentState)

	_, err = sm.NextState(EventFallbackDeleted)
	require.Error(t, err)
}

func TestStateMachine_CooldownExpiry(t *testing.T) {
	fakeNow := time.Now()
	sm := NewStateMachine("pool-123", "4c16g", StateRecovering, 100*time.Millisecond).
		WithClock(func() time.Time { return fakeNow })

	assert.False(t, sm.IsCooldownExpired())
	assert.Greater(t, sm.TimeUntilCooldownExpires(), time.Duration(0))

	// Advance fake clock past cooldown
	fakeNow = fakeNow.Add(150 * time.Millisecond)

	assert.True(t, sm.IsCooldownExpired())
	assert.Equal(t, time.Duration(0), sm.TimeUntilCooldownExpires())
}

func TestStateMachine_RequiresFallback(t *testing.T) {
	tests := []struct {
		state    State
		expected bool
	}{
		{StateNormal, false},
		{StateShortageDetected, true},
		{StateFallbackCreating, true},
		{StateFallbackReady, true},
		{StateRecovering, false},
		{StateDrainingFallback, false},
		{StateDeletingFallback, false},
		{StateDeprecated, false},
	}

	for _, tt := range tests {
		t.Run(string(tt.state), func(t *testing.T) {
			sm := NewStateMachine("pool-123", "4c16g", tt.state, 30*time.Minute)
			assert.Equal(t, tt.expected, sm.RequiresFallback())
		})
	}
}

func TestStateMachine_HasActiveFallback(t *testing.T) {
	tests := []struct {
		state    State
		expected bool
	}{
		{StateNormal, false},
		{StateShortageDetected, false},
		{StateFallbackCreating, false},
		{StateFallbackReady, true},
		{StateRecovering, true},
		{StateDrainingFallback, true},
		{StateDeletingFallback, false},
		{StateDeprecated, false},
	}

	for _, tt := range tests {
		t.Run(string(tt.state), func(t *testing.T) {
			sm := NewStateMachine("pool-123", "4c16g", tt.state, 30*time.Minute)
			assert.Equal(t, tt.expected, sm.HasActiveFallback())
		})
	}
}

func TestStateMachine_IsStable(t *testing.T) {
	tests := []struct {
		state    State
		expected bool
	}{
		{StateNormal, true},
		{StateShortageDetected, false},
		{StateFallbackCreating, false},
		{StateFallbackReady, true},
		{StateRecovering, false},
		{StateDrainingFallback, false},
		{StateDeletingFallback, false},
		{StateDeprecated, true},
	}

	for _, tt := range tests {
		t.Run(string(tt.state), func(t *testing.T) {
			sm := NewStateMachine("pool-123", "4c16g", tt.state, 30*time.Minute)
			assert.Equal(t, tt.expected, sm.IsStable())
		})
	}
}

func TestStateMachine_AllowedEvents(t *testing.T) {
	tests := []struct {
		state    State
		expected []Event
	}{
		{StateNormal, []Event{EventShortageDetected, EventDeprecationStarted}},
		{StateShortageDetected, []Event{EventFallbackCreated, EventCapacityRestored}},
		{StateFallbackCreating, []Event{EventFallbackReady}},
		{StateFallbackReady, []Event{EventCapacityRestored}},
		{StateRecovering, []Event{EventCooldownExpired, EventShortageDetected}},
		{StateDrainingFallback, []Event{EventDrainComplete, EventShortageDetected}},
		{StateDeletingFallback, []Event{EventFallbackDeleted}},
		{StateDeprecated, []Event{}},
	}

	for _, tt := range tests {
		t.Run(string(tt.state), func(t *testing.T) {
			sm := NewStateMachine("pool-123", "4c16g", tt.state, 30*time.Minute)
			events := sm.AllowedEvents()
			assert.ElementsMatch(t, tt.expected, events)
		})
	}
}

func TestIsValidState(t *testing.T) {
	validStates := []State{
		StateNormal, StateShortageDetected, StateFallbackCreating,
		StateFallbackReady, StateRecovering, StateDrainingFallback,
		StateDeletingFallback, StateDeprecated,
	}

	for _, s := range validStates {
		t.Run(string(s), func(t *testing.T) {
			assert.True(t, IsValidState(s))
		})
	}

	assert.False(t, IsValidState(State("invalid")))
	assert.False(t, IsValidState(State("")))
}

func TestStateFromString(t *testing.T) {
	state, err := StateFromString("normal")
	require.NoError(t, err)
	assert.Equal(t, StateNormal, state)

	state, err = StateFromString("fallback_ready")
	require.NoError(t, err)
	assert.Equal(t, StateFallbackReady, state)

	_, err = StateFromString("invalid")
	require.Error(t, err)
}

func TestStateMachine_FullLifecycle(t *testing.T) {
	fakeNow := time.Now()
	sm := NewStateMachine("pool-123", "4c16g", StateNormal, 10*time.Millisecond).
		WithClock(func() time.Time { return fakeNow })

	require.NoError(t, sm.Transition(EventShortageDetected))
	assert.Equal(t, StateShortageDetected, sm.CurrentState)

	require.NoError(t, sm.Transition(EventFallbackCreated))
	assert.Equal(t, StateFallbackCreating, sm.CurrentState)

	require.NoError(t, sm.Transition(EventFallbackReady))
	assert.Equal(t, StateFallbackReady, sm.CurrentState)

	require.NoError(t, sm.Transition(EventCapacityRestored))
	assert.Equal(t, StateRecovering, sm.CurrentState)

	// Advance fake clock past cooldown
	fakeNow = fakeNow.Add(20 * time.Millisecond)

	require.NoError(t, sm.Transition(EventCooldownExpired))
	assert.Equal(t, StateDrainingFallback, sm.CurrentState)

	require.NoError(t, sm.Transition(EventDrainComplete))
	assert.Equal(t, StateDeletingFallback, sm.CurrentState)

	require.NoError(t, sm.Transition(EventFallbackDeleted))
	assert.Equal(t, StateNormal, sm.CurrentState)
}

func TestStateMachine_ShortageInterruptRecovery(t *testing.T) {
	sm := NewStateMachine("pool-123", "4c16g", StateRecovering, 30*time.Minute)

	require.NoError(t, sm.Transition(EventShortageDetected))
	assert.Equal(t, StateFallbackReady, sm.CurrentState)
}

func TestStateMachine_ShortageInterruptDraining(t *testing.T) {
	sm := NewStateMachine("pool-123", "4c16g", StateDrainingFallback, 30*time.Minute)

	require.NoError(t, sm.Transition(EventShortageDetected))
	assert.Equal(t, StateFallbackReady, sm.CurrentState)
}
