package appuser

import (
	"strings"
	"testing"
	"time"

	"hyapp-admin-server/internal/modules/shared"
)

func TestNormalizeListQuerySort(t *testing.T) {
	query := normalizeListQuery(listQuery{Page: 0, PageSize: 1000, SortBy: "coins", SortDirection: "asc"})
	if query.Page != 1 || query.PageSize != 100 || query.SortBy != "coin" || query.SortDirection != "asc" {
		t.Fatalf("coin sort query mismatch: %+v", query)
	}

	query = normalizeListQuery(listQuery{SortBy: "createdAtMs", SortDirection: "unknown"})
	if query.SortBy != "created_at" || query.SortDirection != "desc" {
		t.Fatalf("created sort default mismatch: %+v", query)
	}

	query = normalizeListQuery(listQuery{Country: " ph ", RegionID: -3, RegionIDSet: true, StartMs: -1, EndMs: -2})
	if query.Country != "PH" || query.RegionIDSet || query.StartMs != 0 || query.EndMs != 0 {
		t.Fatalf("filter normalization mismatch: %+v", query)
	}
}

func TestAgeAtUTCBirthdayBoundary(t *testing.T) {
	beforeBirthday := ageAtUTC("2000-07-11", time.Date(2026, time.July, 10, 23, 59, 59, 0, time.UTC))
	if beforeBirthday == nil || *beforeBirthday != 25 {
		t.Fatalf("age before UTC birthday = %v, want 25", beforeBirthday)
	}
	onBirthday := ageAtUTC("2000-07-11", time.Date(2026, time.July, 11, 0, 0, 0, 0, time.UTC))
	if onBirthday == nil || *onBirthday != 26 {
		t.Fatalf("age on UTC birthday = %v, want 26", onBirthday)
	}
	if got := ageAtUTC("", time.Now().UTC()); got != nil {
		t.Fatalf("empty birth age = %v, want nil", got)
	}
}

func TestDisplayUserRolesSupportsMultipleIdentities(t *testing.T) {
	roles := displayUserRoles([]string{"manager", "host", "bd_leader", "host", "unknown"})
	want := []string{"主播", "BD Leader", "经理"}
	if strings.Join(roles, ",") != strings.Join(want, ",") {
		t.Fatalf("roles = %#v, want %#v", roles, want)
	}
	ordinary := displayUserRoles(nil)
	if len(ordinary) != 1 || ordinary[0] != "普通用户" {
		t.Fatalf("ordinary roles = %#v", ordinary)
	}
}

func TestAppUserExportRowKeepsCompositeIdentityFieldsAndAddsColumns(t *testing.T) {
	age := int32(26)
	item := AppUser{
		UserID:               "10001",
		DefaultDisplayUserID: "1299324",
		PrettyDisplayUserID:  "11222",
		Username:             "sumon Ahammad",
		Avatar:               "https://example.test/avatar.png",
		Age:                  &age,
		Roles:                []string{"主播", "经理"},
		Levels: AppUserLevels{
			Wealth: AppUserLevel{RealLevel: 8, DisplayLevel: 14, TemporaryTargetLevel: 14, ExpiresAtMs: 1234},
			Charm:  AppUserLevel{RealLevel: 9, DisplayLevel: 15, TemporaryTargetLevel: 15, ExpiresAtMs: 5678},
			Game:   AppUserLevel{DisplayLevel: 6},
		},
	}
	row := appUserExportRow(item)
	if len(row) != len(appUserExportHeaders()) {
		t.Fatalf("export row columns = %d, headers = %d", len(row), len(appUserExportHeaders()))
	}
	if row[0] != "10001" || row[1] != "1299324" || row[2] != "11222" || row[3] != "sumon Ahammad" || row[4] != "https://example.test/avatar.png" {
		t.Fatalf("identity columns changed: %#v", row[:5])
	}
}

func TestSpreadsheetSafeCSVCellEscapesFormulaPrefixes(t *testing.T) {
	for _, value := range []string{"=HYPERLINK(\"https://example.test\")", "+cmd", " -1+1", "@SUM(A1:A2)"} {
		if got := spreadsheetSafeCSVCell(value); !strings.HasPrefix(got, "'") {
			t.Fatalf("dangerous CSV cell %q was not escaped: %q", value, got)
		}
	}
	if got := spreadsheetSafeCSVCell("normal user"); got != "normal user" {
		t.Fatalf("normal CSV cell changed: %q", got)
	}
}

func TestAppUserListWhereSQLFilters(t *testing.T) {
	whereSQL, args := appUserListWhereSQL("lalu", normalizeListQuery(listQuery{
		Country:     "ph",
		Keyword:     "hunter",
		RegionID:    3,
		RegionIDSet: true,
		StartMs:     1000,
		EndMs:       2000,
		Status:      "active",
	}))
	for _, want := range []string{
		"u.status = ?",
		"CAST(u.user_id AS CHAR) LIKE ?",
		"UPPER(u.country) = ?",
		"u.default_display_user_id LIKE ?",
		"pretty_display_user_id_leases lease",
		"u.region_id = ?",
		"u.created_at_ms >= ?",
		"u.created_at_ms < ?",
	} {
		if !strings.Contains(whereSQL, want) {
			t.Fatalf("where sql missing %q: %s", want, whereSQL)
		}
	}
	if len(args) != 12 ||
		args[0] != "lalu" ||
		args[2] != "%hunter%" ||
		args[3] != "%hunter%" ||
		args[4] != "%hunter%" ||
		args[5] != "%hunter%" ||
		args[7] != "%hunter%" ||
		args[8] != "PH" ||
		args[9] != int64(3) ||
		args[10] != int64(1000) ||
		args[11] != int64(2000) {
		t.Fatalf("where args mismatch: %+v", args)
	}
	if _, ok := args[6].(int64); !ok {
		t.Fatalf("pretty lease expiry arg type = %T, want int64: %+v", args[6], args)
	}

	whereSQL, args = appUserListWhereSQL("lalu", normalizeListQuery(listQuery{RegionID: 0, RegionIDSet: true}))
	if !strings.Contains(whereSQL, "COALESCE(u.region_id, 0) = 0") || !strings.Contains(whereSQL, "NOT EXISTS") {
		t.Fatalf("global region sql mismatch: %s", whereSQL)
	}
	if len(args) != 1 || args[0] != "lalu" {
		t.Fatalf("global region args mismatch: %+v", args)
	}
}

func TestAppUserDisplayIDSQLMatchesDefaultCurrentAndPrettyLease(t *testing.T) {
	whereSQL, args := shared.UserDisplayIDSQL("u", "bd100", 12345)
	for _, want := range []string{
		"u.current_display_user_id = ?",
		"u.default_display_user_id = ?",
		"pretty_display_user_id_leases lease",
		"lease.display_user_id = ?",
		"lease.expires_at_ms > ?",
	} {
		if !strings.Contains(whereSQL, want) {
			t.Fatalf("where sql missing %q: %s", want, whereSQL)
		}
	}
	wantArgs := []any{"bd100", "bd100", int64(12345), "bd100"}
	if len(args) != len(wantArgs) {
		t.Fatalf("args length = %d, want %d: %#v", len(args), len(wantArgs), args)
	}
	for index := range wantArgs {
		if args[index] != wantArgs[index] {
			t.Fatalf("arg[%d] = %#v, want %#v", index, args[index], wantArgs[index])
		}
	}
}

func TestLoginLogWhereSQLUsesSharedUserKeywordFilter(t *testing.T) {
	whereSQL, args := loginLogWhereSQL(nil, loginLogQuery{
		Keyword:       "bd100",
		DisplayUserID: "vip888",
	})
	for _, want := range []string{
		"u.current_display_user_id = ?",
		"u.default_display_user_id = ?",
		"CAST(la.user_id AS CHAR) LIKE ?",
		"u.default_display_user_id LIKE ?",
		"pretty_display_user_id_leases lease",
		"la.client_ip LIKE ?",
	} {
		if !strings.Contains(whereSQL, want) {
			t.Fatalf("where sql missing %q: %s", want, whereSQL)
		}
	}
	if len(args) != 12 || args[1] != "vip888" || args[2] != "vip888" || args[4] != "vip888" || args[5] != "%bd100%" || args[11] != "%bd100%" {
		t.Fatalf("args mismatch: %+v", args)
	}
	if _, ok := args[3].(int64); !ok {
		t.Fatalf("display id pretty expiry arg type = %T, want int64: %+v", args[3], args)
	}
	if _, ok := args[9].(int64); !ok {
		t.Fatalf("keyword pretty expiry arg type = %T, want int64: %+v", args[9], args)
	}
}

func TestAppUserOrderSQL(t *testing.T) {
	asc := appUserOrderSQL(listQuery{SortBy: "created_at", SortDirection: "asc"})
	if asc != "ORDER BY u.created_at_ms ASC, u.user_id ASC" {
		t.Fatalf("created asc order mismatch: %s", asc)
	}

	desc := appUserOrderSQL(listQuery{SortBy: "created_at", SortDirection: "desc"})
	if desc != "ORDER BY u.created_at_ms DESC, u.user_id DESC" {
		t.Fatalf("created desc order mismatch: %s", desc)
	}
}

func TestSortAppUsersByCoin(t *testing.T) {
	items := []AppUser{
		{UserID: "1001", Coin: 10},
		{UserID: "1003", Coin: 10},
		{UserID: "1002", Coin: 80},
	}

	sortAppUsersByCoin(items, "desc")
	if got := []string{items[0].UserID, items[1].UserID, items[2].UserID}; got[0] != "1002" || got[1] != "1003" || got[2] != "1001" {
		t.Fatalf("coin desc order mismatch: %+v", got)
	}

	sortAppUsersByCoin(items, "asc")
	if got := []string{items[0].UserID, items[1].UserID, items[2].UserID}; got[0] != "1001" || got[1] != "1003" || got[2] != "1002" {
		t.Fatalf("coin asc order mismatch: %+v", got)
	}
}
