package permissions_test

import (
	"testing"

	"github.com/owncord/server/permissions"
)

// ─── Constant value tests ─────────────────────────────────────────────────────

// TestPermissionBitValues verifies every constant matches the SCHEMA.md bitfield.
func TestPermissionBitValues(t *testing.T) {
	cases := []struct {
		name     string
		got      int64
		expected int64
	}{
		{"SendMessages", permissions.SendMessages, 0x0001},
		{"ReadMessages", permissions.ReadMessages, 0x0002},
		{"AttachFiles", permissions.AttachFiles, 0x0020},
		{"AddReactions", permissions.AddReactions, 0x0040},
		{"ConnectVoice", permissions.ConnectVoice, 0x0200},
		{"SpeakVoice", permissions.SpeakVoice, 0x0400},
		{"UseVideo", permissions.UseVideo, 0x0800},
		{"ShareScreen", permissions.ShareScreen, 0x1000},
		{"ManageMessages", permissions.ManageMessages, 0x10000},
		{"ManageChannels", permissions.ManageChannels, 0x20000},
		{"KickMembers", permissions.KickMembers, 0x40000},
		{"BanMembers", permissions.BanMembers, 0x80000},
		{"MuteMembers", permissions.MuteMembers, 0x100000},
		{"ManageRoles", permissions.ManageRoles, 0x1000000},
		{"ManageServer", permissions.ManageServer, 0x2000000},
		{"ManageInvites", permissions.ManageInvites, 0x4000000},
		{"ViewAuditLog", permissions.ViewAuditLog, 0x8000000},
		{"Administrator", permissions.Administrator, 0x40000000},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			if tc.got != tc.expected {
				t.Errorf("%s: got 0x%X, want 0x%X", tc.name, tc.got, tc.expected)
			}
		})
	}
}

// TestRoleIDConstants verifies the predefined role IDs match SCHEMA.md defaults.
func TestRoleIDConstants(t *testing.T) {
	cases := []struct {
		name     string
		got      int64
		expected int64
	}{
		{"OwnerRoleID", permissions.OwnerRoleID, 1},
		{"AdminRoleID", permissions.AdminRoleID, 2},
		{"ModeratorRoleID", permissions.ModeratorRoleID, 3},
		{"MemberRoleID", permissions.MemberRoleID, 4},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			if tc.got != tc.expected {
				t.Errorf("%s: got %d, want %d", tc.name, tc.got, tc.expected)
			}
		})
	}
}

// TestOwnerRolePosition verifies the owner position sentinel value.
func TestOwnerRolePosition(t *testing.T) {
	if permissions.OwnerRolePosition != 100 {
		t.Errorf("OwnerRolePosition: got %d, want 100", permissions.OwnerRolePosition)
	}
}

// ─── HasPerm tests ────────────────────────────────────────────────────────────

func TestHasPerm_MatchingBitReturnsTrue(t *testing.T) {
	rolePerms := permissions.SendMessages | permissions.ReadMessages | permissions.ConnectVoice
	if !permissions.HasPerm(rolePerms, permissions.SendMessages) {
		t.Error("expected HasPerm to return true when bit is set")
	}
}

func TestHasPerm_MissingBitReturnsFalse(t *testing.T) {
	rolePerms := permissions.ReadMessages | permissions.ConnectVoice
	if permissions.HasPerm(rolePerms, permissions.SendMessages) {
		t.Error("expected HasPerm to return false when bit is not set")
	}
}

func TestHasPerm_ZeroPermsReturnsFalse(t *testing.T) {
	if permissions.HasPerm(0, permissions.SendMessages) {
		t.Error("expected HasPerm(0, ...) to return false")
	}
}

func TestHasPerm_ZeroRequiredReturnsFalse(t *testing.T) {
	// Requiring perm 0 should never match — 0 is not a valid permission bit.
	if permissions.HasPerm(permissions.Administrator, 0) {
		t.Error("expected HasPerm(..., 0) to return false for zero required perm")
	}
}

func TestHasPerm_MultipleBitsSetOnlyChecksRequired(t *testing.T) {
	// rolePerms has many bits; we ask about one that is present.
	rolePerms := permissions.SendMessages | permissions.ManageMessages | permissions.BanMembers
	if !permissions.HasPerm(rolePerms, permissions.ManageMessages) {
		t.Error("expected HasPerm to find ManageMessages in combined bitfield")
	}
}

func TestHasPerm_AllBitsSet(t *testing.T) {
	// 0x7FFFFFFF (Owner default) must satisfy every individual permission.
	allPerms := int64(0x7FFFFFFF)
	perms := []int64{
		permissions.SendMessages, permissions.ReadMessages, permissions.AttachFiles,
		permissions.AddReactions, permissions.ConnectVoice,
		permissions.SpeakVoice, permissions.UseVideo, permissions.ShareScreen,
		permissions.ManageMessages, permissions.ManageChannels, permissions.KickMembers,
		permissions.BanMembers, permissions.MuteMembers, permissions.ManageRoles,
		permissions.ManageServer, permissions.ManageInvites, permissions.ViewAuditLog,
		permissions.Administrator,
	}
	for _, p := range perms {
		if !permissions.HasPerm(allPerms, p) {
			t.Errorf("expected all-bits owner to have perm 0x%X", p)
		}
	}
}

// ─── HasAdmin tests ───────────────────────────────────────────────────────────

func TestHasAdmin_AdministratorBitSet(t *testing.T) {
	if !permissions.HasAdmin(permissions.Administrator) {
		t.Error("expected HasAdmin to return true when Administrator bit is set")
	}
}

func TestHasAdmin_AdministratorBitWithOthers(t *testing.T) {
	combined := permissions.SendMessages | permissions.Administrator | permissions.BanMembers
	if !permissions.HasAdmin(combined) {
		t.Error("expected HasAdmin to return true with Administrator bit among others")
	}
}

func TestHasAdmin_NoAdministratorBit(t *testing.T) {
	if permissions.HasAdmin(permissions.SendMessages | permissions.BanMembers) {
		t.Error("expected HasAdmin to return false without Administrator bit")
	}
}

func TestHasAdmin_ZeroPerms(t *testing.T) {
	if permissions.HasAdmin(0) {
		t.Error("expected HasAdmin(0) to return false")
	}
}

func TestHasAdmin_AdminRolePermsMissingBit(t *testing.T) {
	// Admin role default is 0x3FFFFFFF — bit 30 (Administrator) is NOT set.
	adminDefault := int64(0x3FFFFFFF)
	if permissions.HasAdmin(adminDefault) {
		t.Error("expected HasAdmin to return false for Admin role (0x3FFFFFFF lacks bit 30)")
	}
}

func TestHasAdmin_OwnerRolePermsHasBit(t *testing.T) {
	// Owner role default is 0x7FFFFFFF — bit 30 IS set.
	ownerDefault := int64(0x7FFFFFFF)
	if !permissions.HasAdmin(ownerDefault) {
		t.Error("expected HasAdmin to return true for Owner role (0x7FFFFFFF has bit 30)")
	}
}

// ─── HasServerPerm tests ──────────────────────────────────────────────────────

// TestHasServerPerm locks the contract api.RequirePermission inherits: admin
// bypass, and ALL-of semantics for multi-bit masks (a raw `perms&mask != 0`
// test would make them any-of).
func TestHasServerPerm(t *testing.T) {
	tests := []struct {
		name      string
		rolePerms int64
		perm      int64
		want      bool
	}{
		{"admin bypasses a bit it lacks", permissions.Administrator, permissions.ManageInvites, true},
		// Deliberately UNLIKE HasPerm(Administrator, 0) == false: the admin
		// bypass short-circuits before the zero-mask guard.
		{"admin with zero mask", permissions.Administrator, 0, true},
		{"exact bit held", permissions.ManageInvites, permissions.ManageInvites, true},
		{"bit not held", permissions.SendMessages, permissions.ManageInvites, false},
		{"non-admin with zero mask", permissions.SendMessages, 0, false},
		{"multi-bit mask partially held", permissions.SendMessages, permissions.SendMessages | permissions.ManageRoles, false},
		{"multi-bit mask fully held", permissions.SendMessages | permissions.ManageRoles, permissions.SendMessages | permissions.ManageRoles, true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := permissions.HasServerPerm(tt.rolePerms, tt.perm); got != tt.want {
				t.Errorf("HasServerPerm() = %v, want %v", got, tt.want)
			}
		})
	}
}

// ─── EffectivePerms tests ─────────────────────────────────────────────────────

// EffectivePerms(rolePerm, allow, deny) = (rolePerm & ^deny) | allow

func TestEffectivePerms_NoOverrides(t *testing.T) {
	base := permissions.SendMessages | permissions.ReadMessages
	got := permissions.EffectivePerms(base, 0, 0)
	if got != base {
		t.Errorf("EffectivePerms with no overrides: got 0x%X, want 0x%X", got, base)
	}
}

func TestEffectivePerms_AllowAddsPermission(t *testing.T) {
	base := permissions.ReadMessages
	allow := permissions.SendMessages
	got := permissions.EffectivePerms(base, allow, 0)
	want := permissions.ReadMessages | permissions.SendMessages
	if got != want {
		t.Errorf("EffectivePerms allow: got 0x%X, want 0x%X", got, want)
	}
}

func TestEffectivePerms_DenyRemovesPermission(t *testing.T) {
	base := permissions.SendMessages | permissions.ReadMessages | permissions.ConnectVoice
	deny := permissions.ConnectVoice
	got := permissions.EffectivePerms(base, 0, deny)
	want := permissions.SendMessages | permissions.ReadMessages
	if got != want {
		t.Errorf("EffectivePerms deny: got 0x%X, want 0x%X", got, want)
	}
}

func TestEffectivePerms_AllowAndDenyTogether(t *testing.T) {
	// deny removes ConnectVoice; allow grants ManageMessages.
	base := permissions.SendMessages | permissions.ReadMessages | permissions.ConnectVoice
	allow := permissions.ManageMessages
	deny := permissions.ConnectVoice
	got := permissions.EffectivePerms(base, allow, deny)
	want := permissions.SendMessages | permissions.ReadMessages | permissions.ManageMessages
	if got != want {
		t.Errorf("EffectivePerms allow+deny: got 0x%X, want 0x%X", got, want)
	}
}

func TestEffectivePerms_AllowOverridesDeny(t *testing.T) {
	// When both allow and deny target the same bit, allow wins
	// because the formula applies deny first, then allow.
	base := permissions.SendMessages
	allow := permissions.ConnectVoice
	deny := permissions.ConnectVoice
	got := permissions.EffectivePerms(base, allow, deny)
	// deny strips ConnectVoice, then allow adds it back.
	want := permissions.SendMessages | permissions.ConnectVoice
	if got != want {
		t.Errorf("EffectivePerms allow overrides deny: got 0x%X, want 0x%X", got, want)
	}
}

func TestEffectivePerms_ZeroBase(t *testing.T) {
	allow := permissions.SendMessages | permissions.ReadMessages
	got := permissions.EffectivePerms(0, allow, 0)
	if got != allow {
		t.Errorf("EffectivePerms zero base: got 0x%X, want 0x%X", got, allow)
	}
}

func TestEffectivePerms_ZeroAll(t *testing.T) {
	got := permissions.EffectivePerms(0, 0, 0)
	if got != 0 {
		t.Errorf("EffectivePerms all zero: got 0x%X, want 0", got)
	}
}

func TestEffectivePerms_DenyAllGrantNone(t *testing.T) {
	base := int64(0x7FFFFFFF)
	deny := int64(0x7FFFFFFF)
	got := permissions.EffectivePerms(base, 0, deny)
	if got != 0 {
		t.Errorf("EffectivePerms deny all: got 0x%X, want 0", got)
	}
}

// ─── HasPerm — combined multi-bit checks ────────────────────────────────────

func TestHasPerm_RequiresAllBitsPresent(t *testing.T) {
	// Require both SendMessages AND ManageMessages; user only has SendMessages.
	rolePerms := permissions.SendMessages | permissions.ReadMessages
	combined := permissions.SendMessages | permissions.ManageMessages

	if permissions.HasPerm(rolePerms, combined) {
		t.Error("should fail when only some of the required bits are present")
	}
}

func TestHasPerm_CombinedBitsAllPresent(t *testing.T) {
	rolePerms := permissions.SendMessages | permissions.ReadMessages | permissions.ManageMessages
	combined := permissions.SendMessages | permissions.ManageMessages

	if !permissions.HasPerm(rolePerms, combined) {
		t.Error("should succeed when all required combined bits are present")
	}
}

// ─── EffectivePerms — channel override edge cases ───────────────────────────

func TestEffectivePerms_DenyAllThenAllowOne(t *testing.T) {
	base := permissions.SendMessages | permissions.ReadMessages | permissions.ConnectVoice
	deny := int64(0x7FFFFFFF)         // deny everything
	allow := permissions.ReadMessages // re-allow just ReadMessages

	eff := permissions.EffectivePerms(base, allow, deny)
	if eff != permissions.ReadMessages {
		t.Errorf("deny-all + allow-one: got 0x%X, want 0x%X", eff, permissions.ReadMessages)
	}
}

func TestEffectivePerms_MultipleDenyMultipleAllow(t *testing.T) {
	base := permissions.SendMessages | permissions.ReadMessages | permissions.AttachFiles | permissions.ConnectVoice
	deny := permissions.SendMessages | permissions.ConnectVoice
	allow := permissions.ManageChannels | permissions.ManageMessages

	eff := permissions.EffectivePerms(base, allow, deny)

	// Should keep: ReadMessages, AttachFiles (not denied)
	// Should lose: SendMessages, ConnectVoice (denied)
	// Should gain: ManageChannels, ManageMessages (allowed)
	want := permissions.ReadMessages | permissions.AttachFiles | permissions.ManageChannels | permissions.ManageMessages
	if eff != want {
		t.Errorf("multi deny+allow: got 0x%X, want 0x%X", eff, want)
	}
}

// ─── Role hierarchy simulation ──────────────────────────────────────────────

func TestRoleHierarchy_OwnerHasMorePermsThanAdmin(t *testing.T) {
	ownerPerms := int64(0x7FFFFFFF) // Owner default
	adminPerms := int64(0x3FFFFFFF) // Admin default (no Administrator bit)

	if !permissions.HasAdmin(ownerPerms) {
		t.Error("owner should be admin")
	}
	if permissions.HasAdmin(adminPerms) {
		t.Error("admin role should NOT have Administrator bit")
	}

	// Owner can ManageServer via admin bypass.
	// Admin can ManageServer via direct bit.
	if !permissions.HasPerm(adminPerms, permissions.ManageServer) {
		t.Error("admin should have ManageServer bit directly")
	}
}

func TestRoleHierarchy_MemberLacksModPerms(t *testing.T) {
	memberPerms := int64(1635) // Default member permissions from schema

	modPerms := []int64{
		permissions.ManageMessages,
		permissions.ManageChannels,
		permissions.KickMembers,
		permissions.BanMembers,
		permissions.ManageRoles,
		permissions.ManageServer,
		permissions.Administrator,
	}

	for _, p := range modPerms {
		if permissions.HasPerm(memberPerms, p) {
			t.Errorf("member (0x%X) should not have permission 0x%X", memberPerms, p)
		}
	}
}

func TestRoleHierarchy_MemberHasBasicPerms(t *testing.T) {
	memberPerms := int64(1635) //nolint:gocritic // documenting the bitmask composition, not commented-out code

	basicPerms := []struct {
		name string
		perm int64
	}{
		{"SendMessages", permissions.SendMessages},
		{"ReadMessages", permissions.ReadMessages},
	}

	for _, tc := range basicPerms {
		t.Run(tc.name, func(t *testing.T) {
			if !permissions.HasPerm(memberPerms, tc.perm) {
				t.Errorf("member should have %s", tc.name)
			}
		})
	}
}

// ─── Permission bits are unique powers of 2 ─────────────────────────────────

func TestPermissionBits_AreDistinctPowersOfTwo(t *testing.T) {
	bits := []int64{
		permissions.SendMessages, permissions.ReadMessages, permissions.AttachFiles,
		permissions.AddReactions, permissions.ConnectVoice,
		permissions.SpeakVoice, permissions.UseVideo, permissions.ShareScreen,
		permissions.ManageMessages, permissions.ManageChannels, permissions.KickMembers,
		permissions.BanMembers, permissions.MuteMembers, permissions.ManageRoles,
		permissions.ManageServer, permissions.ManageInvites, permissions.ViewAuditLog,
		permissions.Administrator,
	}

	seen := make(map[int64]bool)
	for _, b := range bits {
		if b&(b-1) != 0 {
			t.Errorf("permission 0x%X is not a power of 2", b)
		}
		if seen[b] {
			t.Errorf("duplicate permission bit: 0x%X", b)
		}
		seen[b] = true
	}
}

// ─── HasAnyPerm / AdminPerimeter / Name tests ────────────────────────────────

func TestHasAnyPerm_AnyOfSemantics(t *testing.T) {
	rolePerms := permissions.KickMembers | permissions.SendMessages
	// One matching bit is enough, unlike HasPerm's ALL-of.
	if !permissions.HasAnyPerm(rolePerms, permissions.KickMembers|permissions.ManageServer) {
		t.Error("expected HasAnyPerm to match on KickMembers")
	}
	if permissions.HasPerm(rolePerms, permissions.KickMembers|permissions.ManageServer) {
		t.Error("HasPerm must stay ALL-of")
	}
	if permissions.HasAnyPerm(rolePerms, permissions.ManageServer|permissions.ViewAuditLog) {
		t.Error("expected HasAnyPerm to reject a disjoint mask")
	}
	if permissions.HasAnyPerm(rolePerms, 0) {
		t.Error("a zero mask must never be satisfied")
	}
	if permissions.HasAnyPerm(0, permissions.AdminPerimeter) {
		t.Error("a zero role must never satisfy the perimeter")
	}
}

func TestAdminPerimeter_Membership(t *testing.T) {
	admitted := []int64{
		permissions.Administrator, permissions.ManageChannels, permissions.ManageRoles,
		permissions.ManageServer, permissions.ViewAuditLog, permissions.KickMembers,
		permissions.BanMembers, permissions.MuteMembers,
	}
	for _, p := range admitted {
		if !permissions.HasAnyPerm(p, permissions.AdminPerimeter) {
			t.Errorf("%s should admit to the admin perimeter", permissions.Name(p))
		}
	}
	// Bits with no admin-panel route must not open the perimeter.
	refused := []int64{
		permissions.SendMessages, permissions.ReadMessages, permissions.ManageMessages,
		permissions.ManageInvites, permissions.ConnectVoice,
	}
	for _, p := range refused {
		if permissions.HasAnyPerm(p, permissions.AdminPerimeter) {
			t.Errorf("%s must not admit to the admin perimeter", permissions.Name(p))
		}
	}
	// The seeded Moderator role (migration 001) gets in.
	if !permissions.HasAnyPerm(0x000FFFFF, permissions.AdminPerimeter) {
		t.Error("the seeded Moderator mask should admit to the admin perimeter")
	}
	// The seeded Member role does not.
	if permissions.HasAnyPerm(1635, permissions.AdminPerimeter) {
		t.Error("the seeded Member mask must not admit to the admin perimeter")
	}
}

func TestName_KnownAndUnknownBits(t *testing.T) {
	if got := permissions.Name(permissions.ManageChannels); got != "MANAGE_CHANNELS" {
		t.Errorf("Name(ManageChannels) = %q", got)
	}
	if got := permissions.Name(permissions.ViewAuditLog); got != "VIEW_AUDIT_LOG" {
		t.Errorf("Name(ViewAuditLog) = %q", got)
	}
	// Zero, multi-bit and undefined values have no single name.
	for _, bit := range []int64{0, permissions.KickMembers | permissions.BanMembers, 0x4} {
		if got := permissions.Name(bit); got != "UNKNOWN" {
			t.Errorf("Name(0x%X) = %q, want UNKNOWN", bit, got)
		}
	}
}

// TestMentionEveryone_BitIsFreeAndNamed locks phase 3's new bit: 21 was
// unassigned, it is part of AllPerms so an externally supplied mask keeps it,
// and it stays out of the admin perimeter (mentioning is not moderation).
func TestMentionEveryone_BitIsFreeAndNamed(t *testing.T) {
	if permissions.MentionEveryone != 0x200000 {
		t.Errorf("MentionEveryone = 0x%X, want 0x200000 (bit 21)", permissions.MentionEveryone)
	}
	for _, other := range []int64{
		permissions.SendMessages, permissions.ReadMessages, permissions.AttachFiles,
		permissions.AddReactions, permissions.ConnectVoice, permissions.SpeakVoice,
		permissions.UseVideo, permissions.ShareScreen, permissions.ManageMessages,
		permissions.ManageChannels, permissions.KickMembers, permissions.BanMembers,
		permissions.MuteMembers, permissions.ManageRoles, permissions.ManageServer,
		permissions.ManageInvites, permissions.ViewAuditLog, permissions.Administrator,
	} {
		if other&permissions.MentionEveryone != 0 {
			t.Errorf("bit 21 collides with 0x%X", other)
		}
	}
	if permissions.AllPerms&permissions.MentionEveryone == 0 {
		t.Error("AllPerms must include MentionEveryone")
	}
	if permissions.AdminPerimeter&permissions.MentionEveryone != 0 {
		t.Error("MentionEveryone must not admit to the admin perimeter")
	}
	if got := permissions.Name(permissions.MentionEveryone); got != "MENTION_EVERYONE" {
		t.Errorf("Name(MentionEveryone) = %q", got)
	}
}
