package app

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

	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 TestFaultSeverityString(t *testing.T) {
	cases := []struct {
		severity FaultSeverity
		want     string
	}{
		{FaultSeverityWarn, "warn"},
		{FaultSeverityDegraded, "degraded"},
		{FaultSeverityMuted, "muted"},
		{FaultSeverityFaulted, "faulted"},
		{FaultSeverity(99), "unknown"},
	}
	for _, tc := range cases {
		t.Run(tc.want, func(t *testing.T) {
			if got := tc.severity.String(); got != tc.want {
				t.Fatalf("expected %s, got %s", tc.want, got)
			}
			if txt, _ := tc.severity.MarshalText(); string(txt) != tc.want {
				t.Fatalf("MarshalText mismatch: want %s, got %s", tc.want, txt)
			}
		})
	}
}

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

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

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

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

	queue := output.QueueStats{Depth: 5, Health: output.QueueHealthNormal}
	e.evaluateRuntimeState(queue, true)

	last := e.LastFault()
	if last == nil {
		t.Fatal("expected fault recorded for late buffers")
	}
	if last.Reason != FaultReasonLateBuffers {
		t.Fatalf("expected late buffer reason, got %s", last.Reason)
	}
	if last.Severity != FaultSeverityWarn {
		t.Fatalf("expected warn severity, got %s", last.Severity)
	}
}

func TestEngineRecordsWriteTimeoutFault(t *testing.T) {
	cfg := cfgpkg.Default()
	driver := platform.NewSimulatedDriver(&writeErrorBackend{})
	eng := NewEngine(cfg, driver)
	eng.SetChunkDuration(10 * time.Millisecond)

	ctx := context.Background()
	if err := eng.Start(ctx); err != nil {
		t.Fatalf("start: %v", err)
	}
	time.Sleep(120 * time.Millisecond)
	if err := eng.Stop(ctx); err != nil {
		t.Fatalf("stop: %v", err)
	}

	last := eng.LastFault()
	if last == nil {
		t.Fatal("expected write timeout fault")
	}
	if last.Reason != FaultReasonWriteTimeout {
		t.Fatalf("expected writeTimeout reason, got %s", last.Reason)
	}
	if last.Severity != FaultSeverityWarn {
		t.Fatalf("expected warn severity, got %s", last.Severity)
	}
}

type writeErrorBackend struct{}

func (writeErrorBackend) Configure(context.Context, output.BackendConfig) error { return nil }
func (writeErrorBackend) Write(context.Context, *output.CompositeFrame) (int, error) {
	return 0, errors.New("write timeout")
}
func (writeErrorBackend) Flush(context.Context) error { return nil }
func (writeErrorBackend) Close(context.Context) error { return nil }
func (writeErrorBackend) Info() output.BackendInfo {
	return output.BackendInfo{
		Name:        "write-error",
		Description: "backend that rejects writes",
		Capabilities: output.BackendCapabilities{
			SupportsComposite: true,
		},
	}
}
