package core

import (
	"sync"
	"testing"
	"time"
)

func testRoles() []RoleInput {
	return []RoleInput{
		{
			Name:             "admin",
			UserIDs:          []string{"admin1", "admin2"},
			DisabledCommands: []string{},
			RateLimit:        &RateLimitCfg{MaxMessages: 50, Window: time.Minute},
		},
		{
			Name:             "member",
			UserIDs:          []string{"*"},
			DisabledCommands: []string{"*"},
			RateLimit:        &RateLimitCfg{MaxMessages: 3, Window: time.Minute},
		},
	}
}

func TestUserRoleManager_ResolveRole_ExactMatch(t *testing.T) {
	m := NewUserRoleManager()
	m.Configure("member", testRoles())

	role := m.ResolveRole("admin1")
	if role == nil || role.Name != "admin" {
		t.Errorf("expected admin role, got %+v", role)
	}
	role = m.ResolveRole("admin2")
	if role == nil || role.Name != "admin" {
		t.Errorf("expected admin role for admin2, got %+v", role)
	}
}

func TestUserRoleManager_ResolveRole_CaseInsensitive(t *testing.T) {
	m := NewUserRoleManager()
	m.Configure("member", testRoles())

	role := m.ResolveRole("ADMIN1")
	if role == nil || role.Name != "admin" {
		t.Errorf("expected case-insensitive match to admin, got %+v", role)
	}
}

func TestUserRoleManager_ResolveRole_WildcardFallback(t *testing.T) {
	m := NewUserRoleManager()
	m.Configure("member", testRoles())

	role := m.ResolveRole("unknown_user")
	if role == nil || role.Name != "member" {
		t.Errorf("expected member (wildcard) role, got %+v", role)
	}
}

func TestUserRoleManager_ResolveRole_DefaultRole(t *testing.T) {
	// No wildcard role; use defaultRole
	m := NewUserRoleManager()
	m.Configure("viewer", []RoleInput{
		{Name: "admin", UserIDs: []string{"admin1"}, DisabledCommands: []string{}},
		{Name: "viewer", UserIDs: []string{"viewer1"}, DisabledCommands: []string{"shell"}},
	})

	role := m.ResolveRole("unknown_user")
	if role == nil || role.Name != "viewer" {
		t.Errorf("expected default role 'viewer', got %+v", role)
	}
}

func TestUserRoleManager_ResolveRole_NoMatch(t *testing.T) {
	m := NewUserRoleManager()
	m.Configure("", []RoleInput{
		{Name: "admin", UserIDs: []string{"admin1"}, DisabledCommands: []string{}},
	})

	role := m.ResolveRole("unknown_user")
	if role != nil {
		t.Errorf("expected nil for unmatched user, got %+v", role)
	}
}

func TestUserRoleManager_DisabledCmdsWildcard(t *testing.T) {
	m := NewUserRoleManager()
	m.Configure("member", testRoles())

	role := m.ResolveRole("regular_user")
	if role == nil {
		t.Fatal("expected member role")
	}
	// Member has disabled_commands = ["*"], should disable all builtins
	for _, bc := range builtinCommands {
		if !role.DisabledCmds[bc.id] {
			t.Errorf("member role should disable %q", bc.id)
		}
	}
}

func TestUserRoleManager_DisabledCmdsEmpty(t *testing.T) {
	m := NewUserRoleManager()
	m.Configure("member", testRoles())

	role := m.ResolveRole("admin1")
	if role == nil {
		t.Fatal("expected admin role")
	}
	if len(role.DisabledCmds) != 0 {
		t.Errorf("admin role should have no disabled commands, got %d", len(role.DisabledCmds))
	}
}

func TestUserRoleManager_AllowRate_RoleSpecific(t *testing.T) {
	m := NewUserRoleManager()
	m.Configure("member", testRoles())

	// Member has max_messages=3
	for i := 0; i < 3; i++ {
		allowed, handled := m.AllowRate("user1")
		if !allowed || !handled {
			t.Errorf("request %d should be allowed, got allowed=%v handled=%v", i+1, allowed, handled)
		}
	}
	// 4th should be blocked
	allowed, handled := m.AllowRate("user1")
	if allowed || !handled {
		t.Errorf("4th request should be blocked, got allowed=%v handled=%v", allowed, handled)
	}

	// Admin has max_messages=50, should still be allowed
	allowed, handled = m.AllowRate("admin1")
	if !allowed || !handled {
		t.Errorf("admin should be allowed, got allowed=%v handled=%v", allowed, handled)
	}
}

func TestUserRoleManager_AllowRate_NoRoleLimit(t *testing.T) {
	m := NewUserRoleManager()
	m.Configure("viewer", []RoleInput{
		{Name: "viewer", UserIDs: []string{"*"}, DisabledCommands: []string{}},
		// No RateLimit on viewer role
	})

	allowed, handled := m.AllowRate("anyone")
	if !allowed || handled {
		t.Errorf("expected allowed=true handled=false for role without rate_limit, got %v %v", allowed, handled)
	}
}

func TestUserRoleManager_AllowRate_Concurrent(t *testing.T) {
	m := NewUserRoleManager()
	m.Configure("member", testRoles())

	var wg sync.WaitGroup
	for i := 0; i < 50; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			m.AllowRate("concurrent_user")
		}()
	}
	wg.Wait()
}

func TestUserRoleManager_NilReceiver(t *testing.T) {
	var m *UserRoleManager

	if role := m.ResolveRole("anyone"); role != nil {
		t.Error("nil manager should return nil role")
	}
	allowed, handled := m.AllowRate("anyone")
	if !allowed || handled {
		t.Error("nil manager should return allowed=true handled=false")
	}
	m.Stop() // should not panic
}

func TestUserRoleManager_Stop(t *testing.T) {
	m := NewUserRoleManager()
	m.Configure("member", testRoles())

	m.Stop()
	m.Stop() // idempotent

	// AllowRate should still work (just no cleanup goroutine)
	allowed, handled := m.AllowRate("admin1")
	if !allowed || !handled {
		t.Errorf("should still work after Stop, got allowed=%v handled=%v", allowed, handled)
	}
}

func TestUserRoleManager_Snapshot(t *testing.T) {
	m := NewUserRoleManager()
	m.Configure("member", testRoles())

	snap := m.Snapshot()
	if snap["configured"] != true {
		t.Error("expected configured=true")
	}
	if snap["default_role"] != "member" {
		t.Errorf("expected default_role=member, got %v", snap["default_role"])
	}
	roles, ok := snap["roles"].(map[string]any)
	if !ok || len(roles) != 2 {
		t.Errorf("expected 2 roles, got %v", snap["roles"])
	}
}

func TestUserRoleManager_Snapshot_Nil(t *testing.T) {
	var m *UserRoleManager
	snap := m.Snapshot()
	if snap["configured"] != false {
		t.Error("nil manager snapshot should have configured=false")
	}
}

func TestUserRoleManager_ResolveRole_EmptyUserID(t *testing.T) {
	m := NewUserRoleManager()
	m.Configure("member", testRoles())

	// Empty userID should still resolve to default/wildcard role
	role := m.ResolveRole("")
	if role == nil || role.Name != "member" {
		t.Errorf("empty userID should resolve to default role, got %+v", role)
	}
}

func TestValidateRoleInputs_DuplicateUserIDs(t *testing.T) {
	err := ValidateRoleInputs("admin", []RoleInput{
		{Name: "admin", UserIDs: []string{"user1"}},
		{Name: "member", UserIDs: []string{"user1"}},
	})
	if err == nil {
		t.Error("expected error for duplicate user IDs")
	}
}

func TestValidateRoleInputs_MultipleWildcards(t *testing.T) {
	err := ValidateRoleInputs("admin", []RoleInput{
		{Name: "admin", UserIDs: []string{"*"}},
		{Name: "member", UserIDs: []string{"*"}},
	})
	if err == nil {
		t.Error("expected error for multiple wildcards")
	}
}

func TestValidateRoleInputs_InvalidDefaultRole(t *testing.T) {
	err := ValidateRoleInputs("nonexistent", []RoleInput{
		{Name: "admin", UserIDs: []string{"user1"}},
	})
	if err == nil {
		t.Error("expected error for invalid default_role")
	}
}

func TestValidateRoleInputs_EmptyUserIDs(t *testing.T) {
	err := ValidateRoleInputs("admin", []RoleInput{
		{Name: "admin", UserIDs: []string{}},
	})
	if err == nil {
		t.Error("expected error for empty user_ids")
	}
}

func TestValidateRoleInputs_CaseInsensitiveDuplicate(t *testing.T) {
	err := ValidateRoleInputs("admin", []RoleInput{
		{Name: "admin", UserIDs: []string{"User1"}},
		{Name: "member", UserIDs: []string{"user1"}},
	})
	if err == nil {
		t.Error("expected error for case-insensitive duplicate user IDs")
	}
}

func TestValidateRoleInputs_Valid(t *testing.T) {
	err := ValidateRoleInputs("member", testRoles())
	if err != nil {
		t.Errorf("expected no error for valid config, got %v", err)
	}
}
