package runtime_test

import (
	"errors"
	"testing"
	"time"

	"github.com/openlibrecommunity/olcrtc/internal/control"
	"github.com/openlibrecommunity/olcrtc/internal/runtime"
)

func TestSetupCipherErrors(t *testing.T) {
	if _, err := runtime.SetupCipher(""); !errors.Is(err, runtime.ErrKeyRequired) {
		t.Fatalf("empty key error = %v, want ErrKeyRequired", err)
	}
	if _, err := runtime.SetupCipher("notHex"); err == nil {
		t.Fatalf("bad hex error = nil")
	}
	if _, err := runtime.SetupCipher("00"); !errors.Is(err, runtime.ErrKeySize) {
		t.Fatalf("short key error = %v, want ErrKeySize", err)
	}
}

func TestSetupCipherSuccess(t *testing.T) {
	key := "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
	c, err := runtime.SetupCipher(key)
	if err != nil {
		t.Fatalf("SetupCipher() error = %v", err)
	}
	if c == nil {
		t.Fatal("SetupCipher() returned nil cipher")
	}
}

func TestSmuxConfigDefault(t *testing.T) {
	cfg := runtime.SmuxConfig(0)
	if cfg.Version != 2 || cfg.MaxFrameSize != 32768 {
		t.Fatalf("SmuxConfig(0) = %+v", cfg)
	}
	if cfg.KeepAliveDisabled || cfg.KeepAliveInterval != 10*time.Second ||
		cfg.KeepAliveTimeout != 30*time.Second {
		t.Fatalf("SmuxConfig(0) keepalive = %+v", cfg)
	}
}

func TestSmuxConfigShrinks(t *testing.T) {
	// 100-byte wire payload minus smux+crypto overhead is far below default
	// 32768, so MaxFrameSize must shrink.
	cfg := runtime.SmuxConfig(100)
	if cfg.MaxFrameSize >= 32768 {
		t.Fatalf("MaxFrameSize = %d, want shrunk", cfg.MaxFrameSize)
	}
	if cfg.MaxFrameSize+runtime.SmuxWireOverhead != 100 {
		t.Fatalf("wire size = %d, want 100", cfg.MaxFrameSize+runtime.SmuxWireOverhead)
	}
}

func TestHealthTrackerEmitsOnEveryChange(t *testing.T) {
	var got []control.Status
	h := runtime.NewHealthTracker(func(s control.Status) {
		got = append(got, s)
	})

	h.RecordSession("s1")
	h.RecordPong(control.Health{LastSeen: time.Unix(100, 0), RTT: time.Millisecond})
	h.RecordMissed(2)
	h.RecordReconnect()
	h.RecordUnhealthy(3)

	if len(got) != 5 {
		t.Fatalf("notify count = %d, want 5", len(got))
	}
	if got[0].SessionID != "s1" {
		t.Fatalf("first snapshot session id = %q", got[0].SessionID)
	}
	if got[1].LastRTT != time.Millisecond {
		t.Fatalf("second snapshot rtt = %v", got[1].LastRTT)
	}
	final := h.Status()
	if final.Reconnects != 1 || final.UnhealthyEvents != 1 || final.MissedPongs != 3 {
		t.Fatalf("final snapshot = %+v", final)
	}
}

func TestHealthTrackerNilNotifyOK(t *testing.T) {
	h := runtime.NewHealthTracker(nil)
	h.RecordSession("s") // must not panic
	if h.Status().SessionID != "s" {
		t.Fatal("Status() did not record without notify")
	}
}
