package app

import (
	"testing"

	cfgpkg "github.com/jan/fm-rds-tx/internal/config"
	"github.com/jan/fm-rds-tx/internal/output"
	"github.com/jan/fm-rds-tx/internal/platform"
)

func TestEngineRuntimeStateReporting(t *testing.T) {
	e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil))

	if got := e.Stats().State; got != string(RuntimeStateIdle) {
		t.Fatalf("expected initial state idle, got %s", got)
	}

	e.setRuntimeState(RuntimeStatePrebuffering)
	if got := e.Stats().State; got != string(RuntimeStatePrebuffering) {
		t.Fatalf("expected prebuffering, got %s", got)
	}

	e.setRuntimeState(RuntimeStateRunning)
	if got := e.currentRuntimeState(); got != RuntimeStateRunning {
		t.Fatalf("currentRuntimeState mismatch: %s", got)
	}
}

func TestEngineRuntimeStateTransitions(t *testing.T) {
	e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil))
	e.setRuntimeState(RuntimeStatePrebuffering)

	queue := output.QueueStats{Depth: 1, FillLevel: 0.75, Health: output.QueueHealthNormal}
	e.evaluateRuntimeState(queue, false)
	if got := e.currentRuntimeState(); got != RuntimeStateRunning {
		t.Fatalf("expected running after full buffer, got %s", got)
	}

	queue.Health = output.QueueHealthCritical
	for i := 0; i < queueCriticalStreakThreshold; i++ {
		e.evaluateRuntimeState(queue, false)
	}
	if got := e.currentRuntimeState(); got != RuntimeStateDegraded {
		t.Fatalf("expected degraded on queue critical streak, got %s", got)
	}

	queue.Health = output.QueueHealthNormal
	e.evaluateRuntimeState(queue, false)
	if got := e.currentRuntimeState(); got != RuntimeStateRunning {
		t.Fatalf("expected running once queue healthy, got %s", got)
	}

	e.evaluateRuntimeState(queue, true)
	if got := e.currentRuntimeState(); got != RuntimeStateDegraded {
		t.Fatalf("expected degraded when late buffers seen, got %s", got)
	}
}

func TestEngineRuntimeStateMuteOnPersistentQueueCritical(t *testing.T) {
	e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil))
	e.setRuntimeState(RuntimeStateRunning)

	queue := output.QueueStats{Depth: 1, Health: output.QueueHealthCritical}
	for i := 0; i < queueMutedStreakThreshold; i++ {
		e.evaluateRuntimeState(queue, false)
	}

	if got := e.currentRuntimeState(); got != RuntimeStateMuted {
		t.Fatalf("expected muted after prolonged queue critical, got %s", got)
	}

	muteFault := e.LastFault()
	if muteFault == nil {
		t.Fatal("expected fault recorded for the mute transition")
	}
	if muteFault.Reason != FaultReasonQueueCritical {
		t.Fatalf("expected queue critical reason, got %s", muteFault.Reason)
	}
	if muteFault.Severity != FaultSeverityMuted {
		t.Fatalf("expected muted severity, got %s", muteFault.Severity)
	}

	queue.Health = output.QueueHealthNormal
	for i := 0; i < queueMutedRecoveryThreshold-1; i++ {
		e.evaluateRuntimeState(queue, false)
		if got := e.currentRuntimeState(); got != RuntimeStateMuted {
			t.Fatalf("expected still muted while recovery window builds, got %s", got)
		}
	}

	e.evaluateRuntimeState(queue, false)
	if got := e.currentRuntimeState(); got != RuntimeStateDegraded {
		t.Fatalf("expected degrade once mute recovery threshold reached, got %s", got)
	}

	recoveryFault := e.LastFault()
	if recoveryFault == nil {
		t.Fatal("expected recovery fault entry after leaving mute")
	}
	if recoveryFault.Severity != FaultSeverityDegraded {
		t.Fatalf("expected degraded severity for recovery event, got %s", recoveryFault.Severity)
	}
	if recoveryFault.Reason != FaultReasonQueueCritical {
		t.Fatalf("expected queue critical reason for recovery event, got %s", recoveryFault.Reason)
	}

	e.evaluateRuntimeState(queue, false)
	if got := e.currentRuntimeState(); got != RuntimeStateRunning {
		t.Fatalf("expected running after recovery, got %s", got)
	}
}

func TestEngineFaultsAfterMutedCriticalStreak(t *testing.T) {
	e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil))
	e.setRuntimeState(RuntimeStateRunning)

	queue := output.QueueStats{Depth: 1, Health: output.QueueHealthCritical}
	for i := 0; i < queueMutedStreakThreshold; i++ {
		e.evaluateRuntimeState(queue, false)
	}
	if got := e.currentRuntimeState(); got != RuntimeStateMuted {
		t.Fatalf("expected muted after draining critical streak, got %s", got)
	}

	triggered := false
	for i := 0; i < queueFaultedStreakThreshold; i++ {
		e.evaluateRuntimeState(queue, false)
		if e.currentRuntimeState() == RuntimeStateFaulted {
			triggered = true
			break
		}
	}
	if !triggered {
		t.Fatalf("expected faulted after %d extra critical checks", queueFaultedStreakThreshold)
	}
	if got := e.currentRuntimeState(); got != RuntimeStateFaulted {
		t.Fatalf("expected faulted state, got %s", got)
	}

	fault := e.LastFault()
	if fault == nil {
		t.Fatal("expected recorded fault")
	}
	if fault.Severity != FaultSeverityFaulted {
		t.Fatalf("expected faulted severity, got %s", fault.Severity)
	}
	if fault.Reason != FaultReasonQueueCritical {
		t.Fatalf("expected queue critical reason, got %s", fault.Reason)
	}
}

func TestRuntimeTransitionCounters(t *testing.T) {
	e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil))

	if got := e.Stats().DegradedTransitions; got != 0 {
		t.Fatalf("expected zero transitions initially, got %d", got)
	}
	if got := e.Stats().FaultCount; got != 0 {
		t.Fatalf("expected zero faults initially, got %d", got)
	}

	e.setRuntimeState(RuntimeStateDegraded)
	if got := e.Stats().DegradedTransitions; got != 1 {
		t.Fatalf("expected one degraded transition, got %d", got)
	}

	e.setRuntimeState(RuntimeStateMuted)
	if got := e.Stats().MutedTransitions; got != 1 {
		t.Fatalf("expected one mute transition, got %d", got)
	}

	e.setRuntimeState(RuntimeStateFaulted)
	if got := e.Stats().FaultedTransitions; got != 1 {
		t.Fatalf("expected one faulted transition, got %d", got)
	}

	e.recordFault(FaultReasonQueueCritical, FaultSeverityWarn, "audit")
	if got := e.Stats().FaultCount; got != 1 {
		t.Fatalf("expected one recorded fault, got %d", got)
	}
}

func TestEngineTransitionHistory(t *testing.T) {
	e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil))
	e.setRuntimeState(RuntimeStateRunning)
	e.setRuntimeState(RuntimeStateDegraded)
	e.setRuntimeState(RuntimeStateMuted)

	history := e.Stats().TransitionHistory
	if len(history) != 3 {
		t.Fatalf("expected 3 transitions recorded, got %d", len(history))
	}
	if history[0].From != RuntimeStateIdle || history[0].To != RuntimeStateRunning {
		t.Fatalf("unexpected first transition: %+v", history[0])
	}
	if history[0].Severity != "ok" {
		t.Fatalf("expected ok severity for running transition, got %s", history[0].Severity)
	}
	if history[1].To != RuntimeStateDegraded || history[1].Severity != "warn" {
		t.Fatalf("expected degraded transition with warn severity, got %+v", history[1])
	}
	if history[2].To != RuntimeStateMuted || history[2].Severity != "warn" {
		t.Fatalf("expected muted transition with warn severity, got %+v", history[2])
	}
}

func TestEngineResetFaultRequiresFaultedState(t *testing.T) {
	e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil))
	if err := e.ResetFault(); err == nil {
		t.Fatal("expected error when resetting non-faulted state")
	}
}

func TestEngineResetFaultTransitionsToDegraded(t *testing.T) {
	e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil))
	e.criticalStreak.Store(7)
	e.mutedRecoveryStreak.Store(3)
	e.mutedFaultStreak.Store(1)
	e.setRuntimeState(RuntimeStateFaulted)
	if err := e.ResetFault(); err != nil {
		t.Fatalf("reset fault failed: %v", err)
	}
	if got := e.currentRuntimeState(); got != RuntimeStateDegraded {
		t.Fatalf("expected degraded after reset, got %s", got)
	}
	if e.criticalStreak.Load() != 0 {
		t.Fatalf("expected critical streak reset, got %d", e.criticalStreak.Load())
	}
	if e.mutedRecoveryStreak.Load() != 0 {
		t.Fatalf("expected mute recovery streak reset, got %d", e.mutedRecoveryStreak.Load())
	}
	if e.mutedFaultStreak.Load() != 0 {
		t.Fatalf("expected mute fault streak reset, got %d", e.mutedFaultStreak.Load())
	}
	if err := e.ResetFault(); err == nil {
		t.Fatal("expected error when resetting after recovery")
	}
}
