package persistence

import (
	"context"
	"reflect"
	"testing"

	"github.com/nexuscrm/shared/pkg/constants"
)

func TestBuildGroupMembershipClosure(t *testing.T) {
	activeGroups := activeGroupSet("A", "B", "C", "D")
	edges := []groupMembershipDirectEdge{
		{groupID: "A", memberID: "U1", memberType: constants.GroupMembershipClosureMemberTypeUser},
		{groupID: "B", memberID: "A", memberType: constants.GroupMembershipClosureMemberTypeGroup},
		{groupID: "C", memberID: "B", memberType: constants.GroupMembershipClosureMemberTypeGroup},
		{groupID: "D", memberID: "C", memberType: constants.GroupMembershipClosureMemberTypeGroup},
	}

	got := closureMap(buildGroupMembershipClosure(activeGroups, edges))

	want := map[string]int{
		"A|User|U1": 1,
		"B|Group|A": 1,
		"B|User|U1": 2,
		"C|Group|B": 1,
		"C|Group|A": 2,
		"C|User|U1": 3,
		"D|Group|C": 1,
		"D|Group|B": 2,
		"D|Group|A": 3,
		"D|User|U1": 4,
	}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("closure mismatch\nwant: %#v\n got: %#v", want, got)
	}
}

func TestBuildGroupMembershipClosure_CycleSkipsSelfRows(t *testing.T) {
	activeGroups := activeGroupSet("A", "B")
	edges := []groupMembershipDirectEdge{
		{groupID: "A", memberID: "B", memberType: constants.GroupMembershipClosureMemberTypeGroup},
		{groupID: "B", memberID: "A", memberType: constants.GroupMembershipClosureMemberTypeGroup},
		{groupID: "B", memberID: "U1", memberType: constants.GroupMembershipClosureMemberTypeUser},
	}

	got := closureMap(buildGroupMembershipClosure(activeGroups, edges))

	want := map[string]int{
		"A|Group|B": 1,
		"A|User|U1": 2,
		"B|Group|A": 1,
		"B|User|U1": 1,
	}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("closure mismatch\nwant: %#v\n got: %#v", want, got)
	}
	if _, ok := got["A|Group|A"]; ok {
		t.Fatal("closure emitted self-membership row for A")
	}
	if _, ok := got["B|Group|B"]; ok {
		t.Fatal("closure emitted self-membership row for B")
	}
}

func TestBuildGroupMembershipClosure_ShortestDepthWins(t *testing.T) {
	activeGroups := activeGroupSet("A", "B", "C")
	edges := []groupMembershipDirectEdge{
		{groupID: "A", memberID: "U1", memberType: constants.GroupMembershipClosureMemberTypeUser},
		{groupID: "B", memberID: "A", memberType: constants.GroupMembershipClosureMemberTypeGroup},
		{groupID: "C", memberID: "B", memberType: constants.GroupMembershipClosureMemberTypeGroup},
		{groupID: "C", memberID: "U1", memberType: constants.GroupMembershipClosureMemberTypeUser},
	}

	got := closureMap(buildGroupMembershipClosure(activeGroups, edges))

	if got["C|User|U1"] != 1 {
		t.Fatalf("C|User|U1 depth = %d, want shortest direct depth 1", got["C|User|U1"])
	}
}

func TestBuildGroupMembershipClosure_DanglingOrDeletedEdgesExcludedByLoaderInputs(t *testing.T) {
	activeGroups := activeGroupSet("A", "B")
	edges := []groupMembershipDirectEdge{
		{groupID: "A", memberID: "U1", memberType: constants.GroupMembershipClosureMemberTypeUser},
		{groupID: "B", memberID: "A", memberType: constants.GroupMembershipClosureMemberTypeGroup},
	}

	got := closureMap(buildGroupMembershipClosure(activeGroups, edges))

	if _, ok := got["A|User|deleted-user"]; ok {
		t.Fatal("closure emitted an edge not supplied by active loader inputs")
	}
	if got["B|User|U1"] != 2 {
		t.Fatalf("B|User|U1 depth = %d, want 2", got["B|User|U1"])
	}
}

func TestBuildGroupMembershipClosure_RoleBackedGroupsExpandThroughPublicGroups(t *testing.T) {
	activeGroups := map[string]activeGroupMembershipSource{
		"PUB_ROLE":    {},
		"ROLE_GROUP":  {groupType: "Role ", userRoleID: "role-parent"},
		"PUB_NESTED":  {},
		"NESTED":      {},
		"ROLE_SUBORD": {groupType: " RoleAndSubordinates ", userRoleID: "role-parent"},
	}
	activeUsers := map[string]activeUserMembershipSource{
		"U-parent": {userRoleID: "role-parent"},
		"U-child":  {userRoleID: "role-child"},
		"U-leaf":   {userRoleID: "role-leaf"},
		"U-other":  {userRoleID: "role-other"},
	}
	roleParents := map[string]string{
		"role-parent": "",
		"role-child":  "role-parent",
		"role-leaf":   "role-child",
		"role-other":  "",
	}
	edges := []groupMembershipDirectEdge{
		{groupID: "PUB_ROLE", memberID: "ROLE_GROUP", memberType: constants.GroupMembershipClosureMemberTypeGroup},
		{groupID: "PUB_NESTED", memberID: "NESTED", memberType: constants.GroupMembershipClosureMemberTypeGroup},
		{groupID: "NESTED", memberID: "ROLE_SUBORD", memberType: constants.GroupMembershipClosureMemberTypeGroup},
	}
	edges = append(edges, buildRoleBackedGroupEdges(activeGroups, activeUsers, roleParents)...)

	got := closureMap(buildGroupMembershipClosure(activeGroups, edges))

	want := map[string]int{
		"PUB_ROLE|Group|ROLE_GROUP":    1,
		"PUB_ROLE|User|U-parent":       2,
		"ROLE_GROUP|User|U-parent":     1,
		"PUB_NESTED|Group|NESTED":      1,
		"PUB_NESTED|Group|ROLE_SUBORD": 2,
		"PUB_NESTED|User|U-parent":     3,
		"PUB_NESTED|User|U-child":      3,
		"PUB_NESTED|User|U-leaf":       3,
		"NESTED|Group|ROLE_SUBORD":     1,
		"NESTED|User|U-parent":         2,
		"NESTED|User|U-child":          2,
		"NESTED|User|U-leaf":           2,
		"ROLE_SUBORD|User|U-parent":    1,
		"ROLE_SUBORD|User|U-child":     1,
		"ROLE_SUBORD|User|U-leaf":      1,
	}
	for key, depth := range want {
		if got[key] != depth {
			t.Fatalf("%s depth = %d, want %d\nall rows: %#v", key, got[key], depth, got)
		}
	}
	if _, ok := got["ROLE_GROUP|User|U-child"]; ok {
		t.Fatalf("Role group unexpectedly expanded subordinate member: %#v", got)
	}
	if _, ok := got["ROLE_SUBORD|User|U-other"]; ok {
		t.Fatalf("RoleAndSubordinates group unexpectedly expanded unrelated role: %#v", got)
	}
}

func TestBuildGroupMembershipClosure_RoleAndSubordinatesKeepsDescendantsForDeletedRoot(t *testing.T) {
	activeGroups := map[string]activeGroupMembershipSource{
		"ROLE_SUBORD": {groupType: "RoleAndSubordinates", userRoleID: "role-deleted-root"},
	}
	activeUsers := map[string]activeUserMembershipSource{
		"U-child": {userRoleID: "role-child"},
		"U-leaf":  {userRoleID: "role-leaf"},
		"U-other": {userRoleID: "role-other"},
	}
	roleParents := map[string]string{
		"role-child": "role-deleted-root",
		"role-leaf":  "role-child",
		"role-other": "",
	}

	edges := buildRoleBackedGroupEdges(activeGroups, activeUsers, roleParents)
	got := closureMap(buildGroupMembershipClosure(activeGroups, edges))

	want := map[string]int{
		"ROLE_SUBORD|User|U-child": 1,
		"ROLE_SUBORD|User|U-leaf":  1,
	}
	for key, depth := range want {
		if got[key] != depth {
			t.Fatalf("%s depth = %d, want %d\nall rows: %#v", key, got[key], depth, got)
		}
	}
	if _, ok := got["ROLE_SUBORD|User|U-other"]; ok {
		t.Fatalf("RoleAndSubordinates group unexpectedly expanded unrelated role: %#v", got)
	}
}

func TestGroupMembershipClosureUnlockContextDropsCancellationAndValues(t *testing.T) {
	type unsafeKey struct{}

	sourceCtx := context.WithValue(context.Background(), unsafeKey{}, "request-scoped")
	sourceCtx, cancel := context.WithCancel(sourceCtx)
	cancel()

	unlockCtx := groupMembershipClosureUnlockContext(sourceCtx)
	if err := unlockCtx.Err(); err != nil {
		t.Fatalf("unlock context inherited source cancellation: %v", err)
	}
	if _, ok := unlockCtx.Deadline(); ok {
		t.Fatal("unlock context should not inherit source deadline")
	}
	if got := unlockCtx.Value(unsafeKey{}); got != nil {
		t.Fatalf("unsafe request-scoped value leaked into unlock context: %#v", got)
	}
}

func activeGroupSet(ids ...string) map[string]activeGroupMembershipSource {
	out := make(map[string]activeGroupMembershipSource, len(ids))
	for _, id := range ids {
		out[id] = activeGroupMembershipSource{}
	}
	return out
}

func closureMap(rows []groupMembershipClosureRow) map[string]int {
	out := make(map[string]int, len(rows))
	for _, row := range rows {
		out[row.groupID+"|"+row.memberType+"|"+row.memberID] = row.depth
	}
	return out
}
