package permissions

import (
	"context"
	"testing"
)

// buildHierarchy builds a role hierarchy cache from a parent map: child -> parent.
// Pass "" as parent value to indicate a root node.
func buildHierarchy(edges map[string]string) map[string]*string {
	cache := make(map[string]*string)
	for child, parent := range edges {
		if parent == "" {
			cache[child] = nil
		} else {
			p := parent
			cache[child] = &p
		}
	}
	return cache
}

func TestGetRoleDescendants(t *testing.T) {
	// Hierarchy: CEO -> VP -> Manager -> Rep
	cache := buildHierarchy(map[string]string{
		"CEO":     "",
		"VP":      "CEO",
		"Manager": "VP",
		"Rep":     "Manager",
	})

	tests := []struct {
		name    string
		roleID  string
		wantLen int
		wantIDs []string
		notWant []string
	}{
		{
			name:    "CEO gets all descendants",
			roleID:  "CEO",
			wantLen: 3,
			wantIDs: []string{"VP", "Manager", "Rep"},
		},
		{
			name:    "VP gets Manager and Rep",
			roleID:  "VP",
			wantLen: 2,
			wantIDs: []string{"Manager", "Rep"},
			notWant: []string{"CEO"},
		},
		{
			name:    "Manager gets only Rep",
			roleID:  "Manager",
			wantLen: 1,
			wantIDs: []string{"Rep"},
			notWant: []string{"CEO", "VP"},
		},
		{
			name:    "Rep is a leaf — no descendants",
			roleID:  "Rep",
			wantLen: 0,
			wantIDs: []string{},
		},
		{
			name:    "Unknown role — no descendants",
			roleID:  "Unknown",
			wantLen: 0,
			wantIDs: []string{},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, cache)
			got := ps.GetRoleDescendants(tt.roleID)

			if len(got) != tt.wantLen {
				t.Errorf("GetRoleDescendants(%q) returned %d descendants, want %d: %v", tt.roleID, len(got), tt.wantLen, got)
			}

			gotSet := make(map[string]bool, len(got))
			for _, id := range got {
				gotSet[id] = true
			}

			for _, want := range tt.wantIDs {
				if !gotSet[want] {
					t.Errorf("GetRoleDescendants(%q) missing expected %q; got: %v", tt.roleID, want, got)
				}
			}
			for _, notWant := range tt.notWant {
				if gotSet[notWant] {
					t.Errorf("GetRoleDescendants(%q) unexpectedly contains %q", tt.roleID, notWant)
				}
			}
		})
	}
}

func TestGetSubordinateUserIDs_NoDescendants(t *testing.T) {
	// Rep is a leaf — GetSubordinateUserIDs should return empty without calling repo
	cache := buildHierarchy(map[string]string{
		"CEO": "",
		"VP":  "CEO",
		"Rep": "VP",
	})
	ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, cache)
	ids, err := ps.GetSubordinateUserIDs(context.Background(), "Rep")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(ids) != 0 {
		t.Errorf("expected no subordinate user IDs for leaf role, got %v", ids)
	}
}

func TestGetSubordinateUserIDs_WithDescendants(t *testing.T) {
	// CEO -> VP -> Manager; mockUserRepo.GetUserIDsByRoleIDs returns [] always
	cache := buildHierarchy(map[string]string{
		"CEO":     "",
		"VP":      "CEO",
		"Manager": "VP",
	})

	ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, cache)
	ids, err := ps.GetSubordinateUserIDs(context.Background(), "CEO")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	// mockUserRepo.GetUserIDsByRoleIDs always returns [] — just verify no error and non-nil slice
	if ids == nil {
		t.Error("expected non-nil slice from GetSubordinateUserIDs")
	}
}
