package relay

import (
	"errors"
	"testing"
	"time"
)

// register is a test helper: every session must be Register-ed before it can be
// Acquire-d. Sender "s" / receiver "r" unless a test needs specific names.
func register(t *testing.T, m *Manager, id, user string) {
	t.Helper()
	if err := m.Register(id, user, "s", "r"); err != nil {
		t.Fatalf("register %s/%s: %v", id, user, err)
	}
}

func TestManager_AcquireReturnsSameSession(t *testing.T) {
	m := NewManager(8, 1024)
	register(t, m, "s1", "alice")
	s1, err := m.Acquire("s1", "alice", "s")
	if err != nil {
		t.Fatalf("acquire 1: %v", err)
	}
	s2, err := m.Acquire("s1", "alice", "r")
	if err != nil {
		t.Fatalf("acquire 2: %v", err)
	}
	if s1 != s2 {
		t.Error("expected same session pointer on repeat Acquire")
	}
}

// Acquire never mints a session: an id that was never Register-ed is rejected.
// This is the core R1 fix — the old Acquire created one on demand, so any
// authenticated client could conjure sessions for arbitrary ids and exhaust
// every slot.
func TestManager_AcquireUnregisteredRejected(t *testing.T) {
	m := NewManager(8, 1024)
	if _, err := m.Acquire("ghost", "alice", "s"); !errors.Is(err, ErrNotRegistered) {
		t.Errorf("expected ErrNotRegistered, got %v", err)
	}
	if got := m.Active(); got != 0 {
		t.Errorf("a rejected Acquire must not create a session; Active=%d", got)
	}
}

func TestManager_DifferentUserForbidden(t *testing.T) {
	m := NewManager(8, 1024)
	register(t, m, "s1", "alice")
	if _, err := m.Acquire("s1", "mallory", "s"); !errors.Is(err, ErrForbidden) {
		t.Errorf("expected ErrForbidden, got %v", err)
	}
}

// Only the registered sender/receiver device may join (G2). A third device of
// the same account — or a missing device name — is forbidden.
func TestManager_NonParticipantForbidden(t *testing.T) {
	m := NewManager(8, 1024)
	if err := m.Register("s1", "alice", "laptop", "phone"); err != nil {
		t.Fatalf("register: %v", err)
	}
	if _, err := m.Acquire("s1", "alice", "tablet"); !errors.Is(err, ErrForbidden) {
		t.Errorf("non-participant device: expected ErrForbidden, got %v", err)
	}
	if _, err := m.Acquire("s1", "alice", ""); !errors.Is(err, ErrForbidden) {
		t.Errorf("empty device: expected ErrForbidden, got %v", err)
	}
	if _, err := m.Acquire("s1", "alice", "laptop"); err != nil {
		t.Errorf("sender device must be allowed: %v", err)
	}
	if _, err := m.Acquire("s1", "alice", "phone"); err != nil {
		t.Errorf("receiver device must be allowed: %v", err)
	}
}

// Register, not Acquire, now enforces the global cap.
func TestManager_GlobalCapEnforced(t *testing.T) {
	m := NewManager(2, 1024)
	if err := m.Register("a", "u1", "s", "r"); err != nil {
		t.Fatalf("a: %v", err)
	}
	if err := m.Register("b", "u2", "s", "r"); err != nil {
		t.Fatalf("b: %v", err)
	}
	if err := m.Register("c", "u3", "s", "r"); !errors.Is(err, ErrTooManySessions) {
		t.Errorf("expected ErrTooManySessions, got %v", err)
	}
	if got := m.Active(); got != 2 {
		t.Errorf("Active: got %d, want 2", got)
	}
}

// A single user can't monopolise every global slot — the per-user cap stops
// them well before the global cap, leaving room for other users.
func TestManager_PerUserCapEnforced(t *testing.T) {
	m := NewManager(8, 1024) // per-user default is 3
	for i, id := range []string{"a", "b", "c"} {
		if err := m.Register(id, "greedy", "s", "r"); err != nil {
			t.Fatalf("register %d: %v", i, err)
		}
	}
	if err := m.Register("d", "greedy", "s", "r"); !errors.Is(err, ErrTooManySessions) {
		t.Errorf("4th session for one user: expected ErrTooManySessions, got %v", err)
	}
	// A different user is unaffected — global slots remain.
	if err := m.Register("e", "other", "s", "r"); err != nil {
		t.Errorf("other user should still register: %v", err)
	}
}

// Register is idempotent for the same owner (e.g. a retried /fallback) and does
// not consume a second slot.
func TestManager_RegisterIdempotent(t *testing.T) {
	m := NewManager(8, 1024)
	register(t, m, "s1", "alice")
	register(t, m, "s1", "alice")
	if got := m.Active(); got != 1 {
		t.Errorf("duplicate Register must not add a slot; Active=%d", got)
	}
	if err := m.Register("s1", "mallory", "s", "r"); !errors.Is(err, ErrForbidden) {
		t.Errorf("re-register under another user: expected ErrForbidden, got %v", err)
	}
}

func TestManager_ReleaseFreesSlot(t *testing.T) {
	m := NewManager(1, 1024)
	register(t, m, "a", "u")
	m.Release("a")
	if got := m.Active(); got != 0 {
		t.Errorf("Active after release: got %d, want 0", got)
	}
	// A new session can now slot in (both global and per-user counts dropped).
	if err := m.Register("b", "u", "s", "r"); err != nil {
		t.Errorf("register after release: %v", err)
	}
}

func TestManager_ReleaseAbortsSession(t *testing.T) {
	m := NewManager(1, 1024)
	register(t, m, "a", "u")
	s, err := m.Acquire("a", "u", "s") // allocates the ring
	if err != nil {
		t.Fatalf("acquire: %v", err)
	}
	m.Release("a")
	if _, err := s.Write(nil, []byte("x")); !errors.Is(err, ErrClosed) {
		t.Errorf("write after release: got %v, want ErrClosed", err)
	}
}

// The reaper reclaims a session whose last activity is older than the idle TTL,
// dropping both the global and per-user counts so the slot is reusable.
func TestManager_ReaperReclaimsIdle(t *testing.T) {
	m := NewManager(8, 1024)
	register(t, m, "a", "u")

	// Just-registered: a sweep "now" leaves it alone.
	if n := m.reapIdle(time.Now().Unix()); n != 0 {
		t.Errorf("fresh session reclaimed: got %d, want 0", n)
	}
	// Sweep far enough in the future that it's past the idle TTL.
	future := time.Now().Add(DefaultIdleTTL + time.Minute).Unix()
	if n := m.reapIdle(future); n != 1 {
		t.Errorf("idle session not reclaimed: got %d, want 1", n)
	}
	if got := m.Active(); got != 0 {
		t.Errorf("Active after reap: got %d, want 0", got)
	}
	// The per-user slot was freed too.
	if err := m.Register("b", "u", "s", "r"); err != nil {
		t.Errorf("register after reap: %v", err)
	}
}

// The stream is single-consumer: a second concurrent reader is refused so it
// can't drain bytes out from under the first and corrupt the receiver's copy.
func TestSession_SingleReader(t *testing.T) {
	m := NewManager(8, 1024)
	register(t, m, "a", "u")
	s, err := m.Acquire("a", "u", "r")
	if err != nil {
		t.Fatalf("acquire: %v", err)
	}
	if !s.AcquireReader() {
		t.Fatal("first AcquireReader should succeed")
	}
	if s.AcquireReader() {
		t.Error("second AcquireReader must fail while the first holds it")
	}
	s.ReleaseReader()
	if !s.AcquireReader() {
		t.Error("AcquireReader should succeed again after release")
	}
}
