package externaladmin

import (
	"encoding/json"
	"errors"
	"reflect"
	"sort"
	"strings"
	"testing"

	"hyapp-admin-server/internal/model"
)

func TestExternalOperatorIdentityUsesStablePositiveNamespace(t *testing.T) {
	first, err := operatorIDForAccount(1)
	if err != nil {
		t.Fatalf("operator id for first account: %v", err)
	}
	second, err := operatorIDForAccount(2)
	if err != nil {
		t.Fatalf("operator id for second account: %v", err)
	}
	if first == 1 || second == 2 || first == second {
		t.Fatalf("operator ids must be namespaced and unique: first=%d second=%d", first, second)
	}
	if uint64(first) > uint64(1<<63-1) || uint64(second) > uint64(1<<63-1) {
		t.Fatalf("operator ids must fit signed downstream fields: first=%d second=%d", first, second)
	}
	if _, err := operatorIDForAccount(externalOperatorNamespace); !errors.Is(err, ErrInvalidSession) {
		t.Fatalf("out-of-range account id error = %v", err)
	}
	if got := operatorUsername("FAMI", "Operator"); got != "external:fami:operator" {
		t.Fatalf("operator username = %q", got)
	}
	if got := operatorUsername("fami", strings.Repeat("x", 80)); len([]rune(got)) != 64 || !strings.HasPrefix(got, "external:fami:") {
		t.Fatalf("long operator username is not safely prefixed/truncated: %q", got)
	}
}

func TestLinkedUserMarshalsLongIDAsString(t *testing.T) {
	body, err := json.Marshal(LinkedUser{UserID: 9223372036854770000})
	if err != nil {
		t.Fatalf("marshal linked user: %v", err)
	}
	if string(body) != `{"userId":"9223372036854770000","displayUserId":"","defaultDisplayUserId":"","prettyDisplayUserId":"","prettyId":"","username":"","avatar":"","status":""}` {
		t.Fatalf("linked user json = %s", body)
	}
}

func TestDefaultPermissionSnapshotProducesCompleteCapabilityContract(t *testing.T) {
	permissions := DefaultPermissionSnapshot()
	wantPermissions := []string{
		"user:list", "user:update", "user-ban:list", "user:ban", "user:unban",
		"host:list", "host:invite", "agency:list", "agency:invite", "bd:list", "bd:invite", "external-super-admin:list",
		"room:list", "room:update", "room:ban", "privilege:list", "privilege:grant", "banner:create", "banner:update",
		"pretty-id:grant", "user-title:grant", "room-background:grant", "user-level:grant",
	}
	for _, permission := range wantPermissions {
		if !contains(permissions, permission) {
			t.Fatalf("default permission snapshot missing %s", permission)
		}
	}

	capabilities := capabilitiesFor(permissions)
	wantCapabilities := []string{
		"user:list", "user:update", "user-ban:list", "user:ban", "user:unban",
		"host:list", "host:invite", "agency:list", "agency:invite", "bd:list", "bd:invite", "external-super-admin:list",
		"room:list", "room:update", "room:ban", "privilege:list", "privilege:grant", "banner:create", "banner:update",
		"pretty-id:grant", "user-title:grant", "room-background:grant", "user-level:grant",
	}
	sort.Strings(wantCapabilities)
	if !reflect.DeepEqual(capabilities, wantCapabilities) {
		t.Fatalf("capabilities = %#v, want %#v", capabilities, wantCapabilities)
	}
}

func TestExternalIdentityHierarchyUsesExplicitPortalOnlyRoles(t *testing.T) {
	tests := []struct {
		input          string
		wantIdentity   string
		wantParentRole string
		parentRequired bool
	}{
		{input: "", wantIdentity: model.ExternalAdminIdentityCountryManager, wantParentRole: model.ExternalAdminIdentityLocal},
		{input: " COUNTRY_MANAGER ", wantIdentity: model.ExternalAdminIdentityCountryManager, wantParentRole: model.ExternalAdminIdentityLocal},
		{input: "local", wantIdentity: model.ExternalAdminIdentityLocal},
		{input: "external_super_admin", wantIdentity: model.ExternalAdminIdentityExternalSuperAdmin, wantParentRole: model.ExternalAdminIdentityCountryManager, parentRequired: true},
	}
	for _, testCase := range tests {
		identity := normalizeIdentity(testCase.input)
		parentRole, required := requiredParentIdentity(identity)
		if identity != testCase.wantIdentity || parentRole != testCase.wantParentRole || required != testCase.parentRequired {
			t.Fatalf("identity=%q normalized=%q parent=%q required=%t", testCase.input, identity, parentRole, required)
		}
	}
	if got := normalizeIdentity("manager"); got != "" {
		t.Fatalf("ambiguous legacy manager identity must be rejected, got %q", got)
	}
}

func TestFlexibleStringAcceptsShortIDStringAndInteger(t *testing.T) {
	for _, testCase := range []struct {
		body string
		want string
	}{
		{body: `"001234"`, want: "001234"},
		{body: `123456`, want: "123456"},
	} {
		var value FlexibleString
		if err := json.Unmarshal([]byte(testCase.body), &value); err != nil {
			t.Fatalf("unmarshal %s: %v", testCase.body, err)
		}
		if string(value) != testCase.want {
			t.Fatalf("value = %q, want %q", value, testCase.want)
		}
	}
}

func TestExactIdentityDoesNotAcceptNicknameFallback(t *testing.T) {
	user := LinkedUser{
		UserID: 77, DisplayUserID: "1234567", DefaultDisplayUserID: "7654321",
		PrettyDisplayUserID: "8888", PrettyID: "8888", Username: "nickname",
	}
	for _, accepted := range []string{"77", "1234567", "7654321", "8888"} {
		if !matchesExactIdentity(user, accepted) {
			t.Fatalf("exact identity %s rejected", accepted)
		}
	}
	if matchesExactIdentity(user, "nickname") {
		t.Fatal("nickname must not bind an external credential")
	}
}

func contains(values []string, target string) bool {
	for _, value := range values {
		if value == target {
			return true
		}
	}
	return false
}
