import { apiRequest } from "@/shared/api/request";
import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints";
import type {
    AdminJobDto,
    ApiPage,
    AppUserAccessTokenDto,
    AppUserBanPayload,
    AppUserBanRecordDto,
    AppUserDeviceDto,
    AppUserExportJobDto,
    AppUserLevelAdjustmentPayload,
    AppUserLoginLogDto,
    AppUserDto,
    AppUserPasswordPayload,
    AppUserThirdPartyIdentityDto,
    AppUserUnbanPayload,
    AppUserUpdatePayload,
    AdminGrantPrettyDisplayIDResultDto,
    EntityId,
    GeneratePrettyDisplayIDsPayload,
    GrantPrettyDisplayIDPayload,
    PageQuery,
    PrettyDisplayIDDto,
    PrettyDisplayIDGenerationBatchDto,
    PrettyDisplayIDPoolDto,
    PrettyDisplayIDPoolPayload,
    RecyclePrettyDisplayIDPayload,
    SetPrettyDisplayIDStatusPayload,
} from "@/shared/api/types";

export async function listAppUsers(query: PageQuery = {}): Promise<ApiPage<AppUserDto>> {
    const endpoint = API_ENDPOINTS.appListUsers;
    const data = await apiRequest<ApiPage<RawAppUser> & { page_size?: number }>(
        apiEndpointPath(API_OPERATIONS.appListUsers),
        {
            method: endpoint.method,
            query,
        },
    );
    return normalizePage(data, normalizeAppUser);
}

export async function getAppUser(userId: EntityId): Promise<AppUserDto> {
    const endpoint = API_ENDPOINTS.appGetUser;
    const data = await apiRequest<RawAppUser>(apiEndpointPath(API_OPERATIONS.appGetUser, { id: userId }), {
        method: endpoint.method,
    });
    return normalizeAppUser(data);
}

export interface RevokeAppUserResourcePayload {
    reason: string;
}

// 详情页按 entitlement 撤回单个素材，不能复用 grant 级撤销；后者会把同一资源组内的其它素材一并回收。
export function revokeAppUserResource(
    userId: EntityId,
    entitlementId: EntityId,
    payload: RevokeAppUserResourcePayload,
): Promise<unknown> {
    const endpoint = API_ENDPOINTS.revokeAppUserResource;
    return apiRequest<unknown, RevokeAppUserResourcePayload>(
        apiEndpointPath(API_OPERATIONS.revokeAppUserResource, {
            entitlement_id: entitlementId,
            user_id: userId,
        }),
        {
            body: payload,
            method: endpoint.method,
        },
    );
}

export async function listAppUserLoginLogs(query: PageQuery = {}): Promise<ApiPage<AppUserLoginLogDto>> {
    const endpoint = API_ENDPOINTS.appListLoginLogs;
    const data = await apiRequest<ApiPage<RawAppUserLoginLog> & { page_size?: number }>(
        apiEndpointPath(API_OPERATIONS.appListLoginLogs),
        {
            method: endpoint.method,
            query,
        },
    );
    return normalizePage(data, normalizeAppUserLoginLog);
}

export async function listAppUserBans(query: PageQuery = {}): Promise<ApiPage<AppUserBanRecordDto>> {
    const endpoint = API_ENDPOINTS.appListBannedUsers;
    const data = await apiRequest<ApiPage<RawAppUserBanRecord> & { page_size?: number }>(
        apiEndpointPath(API_OPERATIONS.appListBannedUsers),
        {
            method: endpoint.method,
            query,
        },
    );
    return normalizePage(data, normalizeAppUserBanRecord);
}

export async function updateAppUser(userId: EntityId, payload: AppUserUpdatePayload): Promise<AppUserDto> {
    const endpoint = API_ENDPOINTS.appUpdateUser;
    const data = await apiRequest<RawAppUser, AppUserUpdatePayload>(
        apiEndpointPath(API_OPERATIONS.appUpdateUser, { id: userId }),
        {
            body: payload,
            method: endpoint.method,
        },
    );
    return normalizeAppUser(data);
}

export async function adjustAppUserLevels(
    userId: EntityId,
    payload: AppUserLevelAdjustmentPayload,
): Promise<AppUserDto> {
    const endpoint = API_ENDPOINTS.appAdjustUserLevels;
    const data = await apiRequest<RawAppUser, AppUserLevelAdjustmentPayload>(
        apiEndpointPath(API_OPERATIONS.appAdjustUserLevels, { id: userId }),
        { body: payload, method: endpoint.method },
    );
    return normalizeAppUser(data);
}

export async function banAppUser(userId: EntityId, payload: AppUserBanPayload): Promise<AppUserDto> {
    const endpoint = API_ENDPOINTS.appBanUser;
    const data = await apiRequest<RawAppUserStatusMutation, AppUserBanPayload>(
        apiEndpointPath(API_OPERATIONS.appBanUser, { id: userId }),
        { body: payload, method: endpoint.method },
    );
    // 新接口返回 SetUserStatusResult；兼容旧环境直接返回 AppUser，避免滚动发布期间列表补丁丢失 userId。
    return normalizeAppUser(data.user ?? data);
}

export async function unbanAppUser(userId: EntityId, payload: AppUserUnbanPayload = {}): Promise<AppUserDto> {
    const endpoint = API_ENDPOINTS.appUnbanUser;
    const data = await apiRequest<RawAppUserStatusMutation, AppUserUnbanPayload>(
        apiEndpointPath(API_OPERATIONS.appUnbanUser, { id: userId }),
        { body: payload, method: endpoint.method },
    );
    return normalizeAppUser(data.user ?? data);
}

export function createAppUserExport(query: PageQuery = {}): Promise<AppUserExportJobDto> {
    const endpoint = API_ENDPOINTS.appExportUsers;
    return apiRequest<AppUserExportJobDto>(apiEndpointPath(API_OPERATIONS.appExportUsers), {
        method: endpoint.method,
        query,
    });
}

export function getAppUserExportJob(jobId: EntityId): Promise<AdminJobDto> {
    const endpoint = API_ENDPOINTS.getJob;
    return apiRequest<AdminJobDto>(apiEndpointPath(API_OPERATIONS.getJob, { id: jobId }), {
        method: endpoint.method,
    });
}

export function downloadAppUserExport(jobId: EntityId): Promise<Response> {
    return apiRequest(apiEndpointPath(API_OPERATIONS.downloadJobArtifact, { id: jobId }), { raw: true });
}

// 当前 Token 属于敏感凭证，服务端每次签发都会记录后台操作日志；因此只提供按需获取接口，不随详情自动拉取。
export async function issueAppUserAccessToken(userId: EntityId): Promise<AppUserAccessTokenDto> {
    const endpoint = API_ENDPOINTS.appIssueUserAccessToken;
    const data = await apiRequest<RawAppUserAccessToken>(
        apiEndpointPath(API_OPERATIONS.appIssueUserAccessToken, { id: userId }),
        { method: endpoint.method },
    );
    return normalizeAppUserAccessToken(data);
}

export function setAppUserPassword(
    userId: EntityId,
    payload: AppUserPasswordPayload,
): Promise<{ passwordSet: boolean }> {
    const endpoint = API_ENDPOINTS.appSetPassword;
    return apiRequest<{ passwordSet: boolean }, AppUserPasswordPayload>(
        apiEndpointPath(API_OPERATIONS.appSetPassword, { id: userId }),
        {
            body: payload,
            method: endpoint.method,
        },
    );
}

// 靓号管理接口路径全部来自生成的 OpenAPI 端点，页面层只依赖这些函数，避免手写 URL 和契约漂移。
export async function listPrettyDisplayIDPools(query: PageQuery = {}): Promise<ApiPage<PrettyDisplayIDPoolDto>> {
    const endpoint = API_ENDPOINTS.listPrettyIdPools;
    const data = await apiRequest<ApiPage<RawPrettyDisplayIDPool>>(apiEndpointPath(API_OPERATIONS.listPrettyIdPools), {
        method: endpoint.method,
        query,
    });
    return normalizePage(data, normalizePrettyDisplayIDPool);
}

export async function createPrettyDisplayIDPool(payload: PrettyDisplayIDPoolPayload): Promise<PrettyDisplayIDPoolDto> {
    const endpoint = API_ENDPOINTS.createPrettyIdPool;
    const data = await apiRequest<RawPrettyDisplayIDPool, PrettyDisplayIDPoolPayload>(
        apiEndpointPath(API_OPERATIONS.createPrettyIdPool),
        {
            body: payload,
            method: endpoint.method,
        },
    );
    return normalizePrettyDisplayIDPool(data);
}

export async function updatePrettyDisplayIDPool(
    poolId: EntityId,
    payload: PrettyDisplayIDPoolPayload,
): Promise<PrettyDisplayIDPoolDto> {
    const endpoint = API_ENDPOINTS.updatePrettyIdPool;
    const data = await apiRequest<RawPrettyDisplayIDPool, PrettyDisplayIDPoolPayload>(
        apiEndpointPath(API_OPERATIONS.updatePrettyIdPool, { pool_id: poolId }),
        {
            body: payload,
            method: endpoint.method,
        },
    );
    return normalizePrettyDisplayIDPool(data);
}

export async function generatePrettyDisplayIDs(
    poolId: EntityId,
    payload: GeneratePrettyDisplayIDsPayload,
): Promise<PrettyDisplayIDGenerationBatchDto> {
    const endpoint = API_ENDPOINTS.generatePrettyIds;
    const data = await apiRequest<RawPrettyDisplayIDGenerationBatch, GeneratePrettyDisplayIDsPayload>(
        apiEndpointPath(API_OPERATIONS.generatePrettyIds, { pool_id: poolId }),
        {
            body: payload,
            method: endpoint.method,
        },
    );
    return normalizePrettyDisplayIDGenerationBatch(data);
}

export async function listPrettyDisplayIDs(query: PageQuery = {}): Promise<ApiPage<PrettyDisplayIDDto>> {
    const endpoint = API_ENDPOINTS.listPrettyIds;
    const data = await apiRequest<ApiPage<RawPrettyDisplayID>>(apiEndpointPath(API_OPERATIONS.listPrettyIds), {
        method: endpoint.method,
        query,
    });
    return normalizePage(data, normalizePrettyDisplayID);
}

export async function grantPrettyDisplayID(
    payload: GrantPrettyDisplayIDPayload,
): Promise<AdminGrantPrettyDisplayIDResultDto> {
    const endpoint = API_ENDPOINTS.grantPrettyId;
    const data = await apiRequest<RawAdminGrantPrettyDisplayIDResult, GrantPrettyDisplayIDPayload>(
        apiEndpointPath(API_OPERATIONS.grantPrettyId),
        {
            body: payload,
            method: endpoint.method,
        },
    );
    return normalizeAdminGrantPrettyDisplayIDResult(data);
}

export async function recyclePrettyDisplayID(
    prettyId: EntityId,
    payload: RecyclePrettyDisplayIDPayload = {},
): Promise<PrettyDisplayIDDto> {
    const endpoint = API_ENDPOINTS.recyclePrettyId;
    const data = await apiRequest<RawPrettyDisplayID, RecyclePrettyDisplayIDPayload>(
        apiEndpointPath(API_OPERATIONS.recyclePrettyId, { pretty_id: prettyId }),
        {
            body: payload,
            method: endpoint.method,
        },
    );
    return normalizePrettyDisplayID(data);
}

export async function setPrettyDisplayIDStatus(
    prettyId: EntityId,
    payload: SetPrettyDisplayIDStatusPayload,
): Promise<PrettyDisplayIDDto> {
    const endpoint = API_ENDPOINTS.setPrettyIdStatus;
    const data = await apiRequest<RawPrettyDisplayID, SetPrettyDisplayIDStatusPayload>(
        apiEndpointPath(API_OPERATIONS.setPrettyIdStatus, { pretty_id: prettyId }),
        {
            body: payload,
            method: endpoint.method,
        },
    );
    return normalizePrettyDisplayID(data);
}

// 用户相关接口历史上既返回过 snake_case，也返回过 camelCase；在 API 边界归一化后，共享用户组件只消费稳定字段。
interface RawAppUser {
    age?: number | null;
    app_version?: string;
    appVersion?: string;
    app_version_at_ms?: number;
    appVersionAtMs?: number;
    avatar?: string;
    balances?: RawAppUserAssetBalance[];
    ban?: RawAppUserBan;
    birth?: string;
    build_number?: string;
    buildNumber?: string;
    coin?: number;
    country?: string;
    country_display_name?: string;
    countryDisplayName?: string;
    country_name?: string;
    countryName?: string;
    created_at_ms?: number;
    createdAtMs?: number;
    default_display_user_id?: string;
    defaultDisplayUserId?: string;
    devices?: RawAppUserDevice[];
    diamond?: number;
    display_user_id?: string;
    displayUserId?: string;
    equipped_resources?: RawAppUserResource[];
    equippedResources?: RawAppUserResource[];
    gender?: string;
    last_active_at_ms?: number;
    lastActiveAtMs?: number;
    last_login_at_ms?: number;
    lastLoginAtMs?: number;
    last_login_ip?: string;
    lastLoginIp?: string;
    last_login_ip_at_ms?: number;
    lastLoginIpAtMs?: number;
    last_login_ip_country_code?: string;
    lastLoginIpCountryCode?: string;
    last_operated_at_ms?: number;
    lastOperatedAtMs?: number;
    last_operator?: RawAppUserOperator;
    lastOperator?: RawAppUserOperator;
    levels?: RawAppUserLevels;
    pretty_display_user_id?: string;
    prettyDisplayUserId?: string;
    pretty_id?: string;
    prettyId?: string;
    region_id?: number;
    regionId?: number;
    region_name?: string;
    regionName?: string;
    register_device?: string;
    registerDevice?: string;
    register_device_id?: string;
    registerDeviceId?: string;
    register_os_version?: string;
    registerOsVersion?: string;
    resources?: RawAppUserResource[];
    roles?: string[];
    status?: string;
    third_party_identities?: RawAppUserThirdPartyIdentity[];
    thirdPartyIdentities?: RawAppUserThirdPartyIdentity[];
    updated_at_ms?: number;
    updatedAtMs?: number;
    user_id?: EntityId;
    userId?: EntityId;
    username?: string;
    vip?: RawAppUserVIP;
    cumulative_recharge_usd_minor?: number;
    cumulativeRechargeUsdMinor?: number;
}

interface RawAppUserStatusMutation extends RawAppUser {
    user?: RawAppUser;
}

interface RawAppUserThirdPartyIdentity {
    created_at_ms?: number;
    createdAtMs?: number;
    openid?: string;
    provider?: string;
    union_id?: string;
    unionId?: string;
}

interface RawAppUserDevice {
    app_version?: string;
    appVersion?: string;
    build_number?: string;
    buildNumber?: string;
    device_id?: string;
    deviceId?: string;
    device_model?: string;
    deviceModel?: string;
    first_seen_at_ms?: number;
    firstSeenAtMs?: number;
    imei?: string;
    last_seen_at_ms?: number;
    lastSeenAtMs?: number;
    manufacturer?: string;
    os_version?: string;
    osVersion?: string;
    platform?: string;
}

interface RawAppUserAccessToken {
    access_token?: string;
    accessToken?: string;
    device_id?: string;
    deviceId?: string;
    expires_in_sec?: number;
    expiresInSec?: number;
    session_id?: string;
    sessionId?: string;
    session_last_heartbeat_at_ms?: number;
    sessionLastHeartbeatAtMs?: number;
    token_type?: string;
    tokenType?: string;
}

interface RawAppUserLevel {
    display_level?: number;
    displayLevel?: number;
    display_value?: number;
    displayValue?: number;
    expires_at_ms?: number;
    expiresAtMs?: number;
    real_level?: number;
    realLevel?: number;
    real_total_value?: number;
    realTotalValue?: number;
    started_at_ms?: number;
    startedAtMs?: number;
    temporary_level_id?: string;
    temporaryLevelId?: string;
    temporary_target_level?: number;
    temporaryTargetLevel?: number;
    track?: string;
}

interface RawAppUserLevels {
    charm?: RawAppUserLevel;
    game?: RawAppUserLevel;
    wealth?: RawAppUserLevel;
}

interface RawAppUserBan {
    active?: boolean;
    expires_at_ms?: number;
    expiresAtMs?: number;
    id?: string;
    permanent?: boolean;
    reason?: string;
    source?: string;
}

interface RawAppUserOperator {
    action?: string;
    admin_id?: EntityId;
    adminId?: EntityId;
    name?: string;
}

type RawAppUserBrief = RawAppUser;

interface RawAppUserAssetBalance {
    asset_type?: string;
    assetType?: string;
    available_amount?: number;
    availableAmount?: number;
    frozen_amount?: number;
    frozenAmount?: number;
    total_amount?: number;
    totalAmount?: number;
    updated_at_ms?: number;
    updatedAtMs?: number;
    version?: number;
}

interface RawAppUserVIP {
    active?: boolean;
    expires_at_ms?: number;
    expiresAtMs?: number;
    level?: number;
    name?: string;
    started_at_ms?: number;
    startedAtMs?: number;
    updated_at_ms?: number;
    updatedAtMs?: number;
}

interface RawAppUserResource {
    animation_url?: string;
    animationUrl?: string;
    asset_url?: string;
    assetUrl?: string;
    created_at_ms?: number;
    createdAtMs?: number;
    effective_at_ms?: number;
    effectiveAtMs?: number;
    entitlement_id?: string;
    entitlementId?: string;
    equipped?: boolean;
    expires_at_ms?: number;
    expiresAtMs?: number;
    name?: string;
    preview_url?: string;
    previewUrl?: string;
    quantity?: number;
    remaining_quantity?: number;
    remainingQuantity?: number;
    resource_code?: string;
    resourceCode?: string;
    resource_id?: number;
    resourceId?: number;
    resource_type?: string;
    resourceType?: string;
    source_grant_id?: string;
    sourceGrantId?: string;
    status?: string;
    updated_at_ms?: number;
    updatedAtMs?: number;
}

interface RawAppUserBanOperator extends RawAppUserBrief {
    account?: string;
    admin_id?: EntityId;
    adminId?: EntityId;
    name?: string;
    type?: string;
}

interface RawAppUserBanRecord {
    created_at_ms?: number;
    createdAtMs?: number;
    id?: number;
    new_status?: string;
    newStatus?: string;
    old_status?: string;
    oldStatus?: string;
    operator?: RawAppUserBanOperator | null;
    reason?: string;
    request_id?: string;
    requestId?: string;
    target?: RawAppUserBrief;
}

interface RawAppUserLoginLog extends RawAppUserBrief {
    block_reason?: string;
    blockReason?: string;
    blocked?: boolean;
    channel?: string;
    country?: string;
    created_at_ms?: number;
    createdAtMs?: number;
    failure_code?: string;
    failureCode?: string;
    id?: number;
    ip_country_code?: string;
    ipCountryCode?: string;
    login_ip?: string;
    loginIp?: string;
    login_type?: string;
    loginType?: string;
    platform?: string;
    provider?: string;
    region_id?: number;
    regionId?: number;
    region_name?: string;
    regionName?: string;
    request_id?: string;
    requestId?: string;
    result?: string;
    risk_highlighted?: boolean;
    riskHighlighted?: boolean;
}

// Go 后台当前按 snake_case JSON 返回，生成类型和页面代码使用 camelCase；Raw 类型同时接收两种形态，便于后续切换生成客户端。
interface RawPrettyDisplayIDPool {
    app_code?: string;
    appCode?: string;
    created_at_ms?: number;
    createdAtMs?: number;
    created_by_admin_id?: EntityId;
    createdByAdminId?: EntityId;
    level_track?: string;
    levelTrack?: string;
    max_level?: number;
    maxLevel?: number;
    min_level?: number;
    minLevel?: number;
    name?: string;
    pool_id?: string;
    poolId?: string;
    rule_config_json?: string;
    ruleConfigJson?: string;
    rule_type?: string;
    ruleType?: string;
    sort_order?: number;
    sortOrder?: number;
    status?: string;
    updated_at_ms?: number;
    updatedAtMs?: number;
    updated_by_admin_id?: EntityId;
    updatedByAdminId?: EntityId;
}

interface RawPrettyDisplayID {
    app_code?: string;
    appCode?: string;
    assigned_at_ms?: number;
    assignedAtMs?: number;
    assigned_lease_id?: string;
    assignedLeaseId?: string;
    assigned_user?: RawAppUserBrief;
    assignedUser?: RawAppUserBrief;
    assigned_user_id?: EntityId;
    assignedUserId?: EntityId;
    created_at_ms?: number;
    createdAtMs?: number;
    created_by_admin_id?: EntityId;
    createdByAdminId?: EntityId;
    display_user_id?: string;
    displayUserId?: string;
    generated_batch_id?: string;
    generatedBatchId?: string;
    operator?: RawPrettyDisplayIDOperator | null;
    pool?: RawPrettyDisplayIDPool | null;
    pool_id?: string;
    poolId?: string;
    pretty_id?: string;
    prettyId?: string;
    release_reason?: string;
    releaseReason?: string;
    released_at_ms?: number;
    releasedAtMs?: number;
    source?: string;
    status?: string;
    updated_at_ms?: number;
    updatedAtMs?: number;
    updated_by_admin_id?: EntityId;
    updatedByAdminId?: EntityId;
}

interface RawPrettyDisplayIDOperator extends RawAppUserBrief {
    account?: string;
    admin_id?: EntityId;
    adminId?: EntityId;
    name?: string;
    source?: string;
    type?: string;
}

interface RawPrettyDisplayIDGenerationBatch {
    app_code?: string;
    appCode?: string;
    batch_id?: string;
    batchId?: string;
    created_at_ms?: number;
    createdAtMs?: number;
    generated_count?: number;
    generatedCount?: number;
    operator_admin_id?: EntityId;
    operatorAdminId?: EntityId;
    pool_id?: string;
    poolId?: string;
    request_id?: string;
    requestId?: string;
    requested_count?: number;
    requestedCount?: number;
    rule_config_json?: string;
    ruleConfigJson?: string;
    rule_type?: string;
    ruleType?: string;
    skipped_conflict_count?: number;
    skippedConflictCount?: number;
    status?: string;
    updated_at_ms?: number;
    updatedAtMs?: number;
}

interface RawAdminGrantPrettyDisplayIDResult {
    identity?: {
        default_display_user_id?: string;
        defaultDisplayUserId?: string;
        display_user_id?: string;
        displayUserId?: string;
        pretty_display_user_id?: string;
        prettyDisplayUserId?: string;
        pretty_id?: string;
        prettyId?: string;
        user_id?: EntityId;
        userId?: EntityId;
    };
    lease_id?: string;
    leaseId?: string;
    pretty_id?: string;
    prettyId?: string;
}

// 分页结构也做一次兼容，page_size 来自 Go 响应，pageSize 来自前端 ApiPage 约定。
function normalizePage<TRaw, TItem>(
    data: ApiPage<TRaw> & { page_size?: number },
    normalize: (item: TRaw) => TItem,
): ApiPage<TItem> {
    return {
        items: (data.items || []).map(normalize),
        page: Number(data.page || 1),
        pageSize: Number(data.pageSize || data.page_size || 20),
        total: Number(data.total || 0),
    };
}

function normalizeAppUser(item: RawAppUser = {}): AppUserDto {
    return {
        age: optionalNumberValue(item.age),
        appVersion: stringValue(item.appVersion ?? item.app_version),
        appVersionAtMs: numberValue(item.appVersionAtMs ?? item.app_version_at_ms),
        avatar: stringValue(item.avatar),
        balances: (item.balances || []).map(normalizeAppUserAssetBalance),
        ban: normalizeAppUserBan(item.ban),
        birth: stringValue(item.birth),
        buildNumber: stringValue(item.buildNumber ?? item.build_number),
        coin: numberValue(item.coin),
        country: stringValue(item.country),
        countryDisplayName: stringValue(item.countryDisplayName ?? item.country_display_name),
        countryName: stringValue(item.countryName ?? item.country_name),
        createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
        defaultDisplayUserId: stringValue(item.defaultDisplayUserId ?? item.default_display_user_id),
        devices: (item.devices || []).map(normalizeAppUserDevice),
        diamond: numberValue(item.diamond),
        displayUserId: stringValue(item.displayUserId ?? item.display_user_id),
        equippedResources: (item.equippedResources ?? item.equipped_resources ?? []).map(normalizeAppUserResource),
        gender: stringValue(item.gender),
        lastActiveAtMs: numberValue(item.lastActiveAtMs ?? item.last_active_at_ms),
        lastLoginAtMs: numberValue(item.lastLoginAtMs ?? item.last_login_at_ms),
        lastLoginIp: stringValue(item.lastLoginIp ?? item.last_login_ip),
        lastLoginIpAtMs: numberValue(item.lastLoginIpAtMs ?? item.last_login_ip_at_ms),
        lastLoginIpCountryCode: stringValue(item.lastLoginIpCountryCode ?? item.last_login_ip_country_code),
        lastOperatedAtMs: numberValue(item.lastOperatedAtMs ?? item.last_operated_at_ms),
        lastOperator: normalizeAppUserOperator(item.lastOperator ?? item.last_operator),
        levels: normalizeAppUserLevels(item.levels),
        prettyDisplayUserId: stringValue(item.prettyDisplayUserId ?? item.pretty_display_user_id),
        prettyId: stringValue(item.prettyId ?? item.pretty_id),
        regionId: numberValue(item.regionId ?? item.region_id),
        regionName: stringValue(item.regionName ?? item.region_name),
        registerDevice: stringValue(item.registerDevice ?? item.register_device),
        registerDeviceId: stringValue(item.registerDeviceId ?? item.register_device_id),
        registerOsVersion: stringValue(item.registerOsVersion ?? item.register_os_version),
        resources: (item.resources || []).map(normalizeAppUserResource),
        roles: Array.isArray(item.roles) ? item.roles.map(stringValue).filter(Boolean) : [],
        status: stringValue(item.status),
        thirdPartyIdentities: (item.thirdPartyIdentities ?? item.third_party_identities ?? []).map(
            normalizeAppUserThirdPartyIdentity,
        ),
        updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_at_ms),
        userId: stringValue(item.userId ?? item.user_id),
        username: stringValue(item.username),
        vip: normalizeAppUserVIP(item.vip),
        cumulativeRechargeUsdMinor: numberValue(
            item.cumulativeRechargeUsdMinor ?? item.cumulative_recharge_usd_minor,
        ),
    };
}

function normalizeAppUserThirdPartyIdentity(item: RawAppUserThirdPartyIdentity = {}): AppUserThirdPartyIdentityDto {
    return {
        createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
        openid: stringValue(item.openid),
        provider: stringValue(item.provider),
        unionId: stringValue(item.unionId ?? item.union_id),
    };
}

function normalizeAppUserDevice(item: RawAppUserDevice = {}): AppUserDeviceDto {
    return {
        appVersion: stringValue(item.appVersion ?? item.app_version),
        buildNumber: stringValue(item.buildNumber ?? item.build_number),
        deviceId: stringValue(item.deviceId ?? item.device_id),
        deviceModel: stringValue(item.deviceModel ?? item.device_model),
        firstSeenAtMs: numberValue(item.firstSeenAtMs ?? item.first_seen_at_ms),
        imei: stringValue(item.imei),
        lastSeenAtMs: numberValue(item.lastSeenAtMs ?? item.last_seen_at_ms),
        manufacturer: stringValue(item.manufacturer),
        osVersion: stringValue(item.osVersion ?? item.os_version),
        platform: stringValue(item.platform),
    };
}

// Token 响应同样做 snake/camel 双形态兼容，复制按钮必须拿到完整 accessToken，不能只保留截断展示值。
function normalizeAppUserAccessToken(item: RawAppUserAccessToken = {}): AppUserAccessTokenDto {
    return {
        accessToken: stringValue(item.accessToken ?? item.access_token),
        deviceId: stringValue(item.deviceId ?? item.device_id),
        expiresInSec: numberValue(item.expiresInSec ?? item.expires_in_sec),
        sessionId: stringValue(item.sessionId ?? item.session_id),
        sessionLastHeartbeatAtMs: numberValue(item.sessionLastHeartbeatAtMs ?? item.session_last_heartbeat_at_ms),
        tokenType: stringValue(item.tokenType ?? item.token_type),
    };
}

function normalizeAppUserLevels(item: RawAppUserLevels = {}) {
    return {
        charm: normalizeAppUserLevel(item.charm, "charm"),
        game: normalizeAppUserLevel(item.game, "game"),
        wealth: normalizeAppUserLevel(item.wealth, "wealth"),
    };
}

function normalizeAppUserLevel(item: RawAppUserLevel = {}, fallbackTrack: string) {
    return {
        displayLevel: numberValue(item.displayLevel ?? item.display_level),
        displayValue: numberValue(item.displayValue ?? item.display_value),
        expiresAtMs: numberValue(item.expiresAtMs ?? item.expires_at_ms),
        realLevel: numberValue(item.realLevel ?? item.real_level),
        realTotalValue: numberValue(item.realTotalValue ?? item.real_total_value),
        startedAtMs: numberValue(item.startedAtMs ?? item.started_at_ms),
        temporaryLevelId: stringValue(item.temporaryLevelId ?? item.temporary_level_id),
        temporaryTargetLevel: numberValue(item.temporaryTargetLevel ?? item.temporary_target_level),
        track: stringValue(item.track || fallbackTrack),
    };
}

function normalizeAppUserBan(item: RawAppUserBan = {}) {
    return {
        active: Boolean(item.active),
        expiresAtMs: numberValue(item.expiresAtMs ?? item.expires_at_ms),
        id: stringValue(item.id),
        permanent: Boolean(item.permanent),
        reason: stringValue(item.reason),
        source: stringValue(item.source),
    };
}

function normalizeAppUserOperator(item?: RawAppUserOperator) {
    if (!item) {
        return undefined;
    }
    return {
        action: stringValue(item.action),
        adminId: stringValue(item.adminId ?? item.admin_id),
        name: stringValue(item.name),
    };
}

function normalizeAppUserAssetBalance(item: RawAppUserAssetBalance = {}) {
    const availableAmount = numberValue(item.availableAmount ?? item.available_amount);
    const frozenAmount = numberValue(item.frozenAmount ?? item.frozen_amount);
    return {
        assetType: stringValue(item.assetType ?? item.asset_type).toUpperCase(),
        availableAmount,
        frozenAmount,
        totalAmount: numberValue(item.totalAmount ?? item.total_amount ?? availableAmount + frozenAmount),
        updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_at_ms),
        version: numberValue(item.version),
    };
}

function normalizeAppUserVIP(item: RawAppUserVIP = {}) {
    return {
        active: Boolean(item.active),
        expiresAtMs: numberValue(item.expiresAtMs ?? item.expires_at_ms),
        level: numberValue(item.level),
        name: stringValue(item.name),
        startedAtMs: numberValue(item.startedAtMs ?? item.started_at_ms),
        updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_at_ms),
    };
}

function normalizeAppUserResource(item: RawAppUserResource = {}) {
    return {
        animationUrl: stringValue(item.animationUrl ?? item.animation_url),
        assetUrl: stringValue(item.assetUrl ?? item.asset_url),
        createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
        effectiveAtMs: numberValue(item.effectiveAtMs ?? item.effective_at_ms),
        entitlementId: stringValue(item.entitlementId ?? item.entitlement_id),
        equipped: Boolean(item.equipped),
        expiresAtMs: numberValue(item.expiresAtMs ?? item.expires_at_ms),
        name: stringValue(item.name),
        previewUrl: stringValue(item.previewUrl ?? item.preview_url),
        quantity: numberValue(item.quantity),
        remainingQuantity: numberValue(item.remainingQuantity ?? item.remaining_quantity),
        resourceCode: stringValue(item.resourceCode ?? item.resource_code),
        resourceId: numberValue(item.resourceId ?? item.resource_id),
        resourceType: stringValue(item.resourceType ?? item.resource_type),
        sourceGrantId: stringValue(item.sourceGrantId ?? item.source_grant_id),
        status: stringValue(item.status),
        updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_at_ms),
    };
}

function normalizeAppUserBrief(item: RawAppUserBrief = {}) {
    const user = normalizeAppUser(item);
    return {
        avatar: user.avatar,
        defaultDisplayUserId: user.defaultDisplayUserId,
        displayUserId: user.displayUserId,
        prettyDisplayUserId: user.prettyDisplayUserId,
        prettyId: user.prettyId,
        userId: user.userId,
        username: user.username,
    };
}

function normalizeAppUserBanOperator(item?: RawAppUserBanOperator | null) {
    if (!item) {
        return undefined;
    }
    const user = normalizeAppUserBrief(item);
    return {
        ...user,
        account: stringValue(item.account),
        adminId: stringValue(item.adminId ?? item.admin_id),
        name: stringValue(item.name),
        type: stringValue(item.type || "system"),
    };
}

function normalizePrettyDisplayIDOperator(item?: RawPrettyDisplayIDOperator | null, fallbackAdminId?: EntityId) {
    if (!item) {
        const adminId = stringValue(fallbackAdminId);
        return adminId
            ? {
                  adminId,
                  name: "后台管理员",
                  type: "admin",
              }
            : undefined;
    }
    const user = normalizeAppUserBrief(item);
    const adminId = stringValue(item.adminId ?? item.admin_id ?? fallbackAdminId);
    return {
        ...user,
        account: stringValue(item.account),
        adminId,
        name: stringValue(item.name),
        source: stringValue(item.source),
        type: stringValue(item.type || (adminId ? "admin" : "app_user")),
    };
}

function normalizeOptionalAppUserBrief(item?: RawAppUserBrief | null) {
    if (!item) {
        return undefined;
    }
    const user = normalizeAppUserBrief(item);
    return nonZeroStringValue(user.userId) || nonZeroStringValue(user.displayUserId) || user.username ? user : undefined;
}

function normalizeAppUserBanRecord(item: RawAppUserBanRecord = {}): AppUserBanRecordDto {
    return {
        createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
        id: numberValue(item.id),
        newStatus: stringValue(item.newStatus ?? item.new_status),
        oldStatus: stringValue(item.oldStatus ?? item.old_status),
        operator: normalizeAppUserBanOperator(item.operator),
        reason: stringValue(item.reason),
        requestId: stringValue(item.requestId ?? item.request_id),
        target: normalizeAppUserBrief(item.target || {}),
    };
}

function normalizeAppUserLoginLog(item: RawAppUserLoginLog = {}): AppUserLoginLogDto {
    const user = normalizeAppUserBrief(item);
    return {
        ...user,
        blockReason: stringValue(item.blockReason ?? item.block_reason),
        blocked: Boolean(item.blocked),
        channel: stringValue(item.channel),
        country: stringValue(item.country),
        createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
        failureCode: stringValue(item.failureCode ?? item.failure_code),
        id: numberValue(item.id),
        ipCountryCode: stringValue(item.ipCountryCode ?? item.ip_country_code),
        loginIp: stringValue(item.loginIp ?? item.login_ip),
        loginType: stringValue(item.loginType ?? item.login_type),
        platform: stringValue(item.platform),
        provider: stringValue(item.provider),
        regionId: numberValue(item.regionId ?? item.region_id),
        regionName: stringValue(item.regionName ?? item.region_name),
        requestId: stringValue(item.requestId ?? item.request_id),
        result: stringValue(item.result),
        riskHighlighted: Boolean(item.riskHighlighted ?? item.risk_highlighted),
    };
}

// 池 DTO 在这里完成字段名和数值兜底，页面表格不再关心后端是否省略可选字段。
function normalizePrettyDisplayIDPool(item: RawPrettyDisplayIDPool): PrettyDisplayIDPoolDto {
    return {
        appCode: item.appCode ?? item.app_code ?? "",
        createdAtMs: item.createdAtMs ?? item.created_at_ms ?? 0,
        createdByAdminId: item.createdByAdminId ?? item.created_by_admin_id ?? "",
        levelTrack: item.levelTrack ?? item.level_track ?? "",
        maxLevel: Number(item.maxLevel ?? item.max_level ?? 0),
        minLevel: Number(item.minLevel ?? item.min_level ?? 0),
        name: item.name ?? "",
        poolId: item.poolId ?? item.pool_id ?? "",
        ruleConfigJson: item.ruleConfigJson ?? item.rule_config_json ?? "",
        ruleType: item.ruleType ?? item.rule_type ?? "",
        sortOrder: Number(item.sortOrder ?? item.sort_order ?? 0),
        status: item.status ?? "",
        updatedAtMs: item.updatedAtMs ?? item.updated_at_ms ?? 0,
        updatedByAdminId: item.updatedByAdminId ?? item.updated_by_admin_id ?? "",
    };
}

function stringValue(value: unknown): string {
    return value === undefined || value === null ? "" : String(value).trim();
}

function numberValue(value: unknown): number {
    const parsed = Number(value || 0);
    return Number.isFinite(parsed) ? parsed : 0;
}

function optionalNumberValue(value: unknown): number | undefined {
    if (value === undefined || value === null || value === "") {
        return undefined;
    }
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : undefined;
}

// 靓号明细包含池号和后台发放两类来源，pool 可能为空，归一化后页面只判断 source/status 即可。
function normalizePrettyDisplayID(item: RawPrettyDisplayID): PrettyDisplayIDDto {
    const createdByAdminId = item.createdByAdminId ?? item.created_by_admin_id ?? "";
    const updatedByAdminId = item.updatedByAdminId ?? item.updated_by_admin_id ?? "";
    return {
        appCode: item.appCode ?? item.app_code ?? "",
        assignedAtMs: item.assignedAtMs ?? item.assigned_at_ms ?? 0,
        assignedLeaseId: item.assignedLeaseId ?? item.assigned_lease_id ?? "",
        assignedUser: normalizeOptionalAppUserBrief(item.assignedUser ?? item.assigned_user),
        assignedUserId: optionalEntityID(item.assignedUserId ?? item.assigned_user_id),
        createdAtMs: item.createdAtMs ?? item.created_at_ms ?? 0,
        createdByAdminId,
        displayUserId: item.displayUserId ?? item.display_user_id ?? "",
        generatedBatchId: item.generatedBatchId ?? item.generated_batch_id ?? "",
        operator: normalizePrettyDisplayIDOperator(item.operator, updatedByAdminId || createdByAdminId),
        pool: item.pool ? normalizePrettyDisplayIDPool(item.pool) : null,
        poolId: item.poolId ?? item.pool_id ?? "",
        prettyId: item.prettyId ?? item.pretty_id ?? "",
        releaseReason: item.releaseReason ?? item.release_reason ?? "",
        releasedAtMs: item.releasedAtMs ?? item.released_at_ms ?? 0,
        source: item.source ?? "",
        status: item.status ?? "",
        updatedAtMs: item.updatedAtMs ?? item.updated_at_ms ?? 0,
        updatedByAdminId,
    };
}

// 生成结果用于展示本次批量生成的真实落库数量和冲突跳过数量，数值统一转换，避免字符串数字影响统计。
function normalizePrettyDisplayIDGenerationBatch(
    item: RawPrettyDisplayIDGenerationBatch,
): PrettyDisplayIDGenerationBatchDto {
    return {
        appCode: item.appCode ?? item.app_code ?? "",
        batchId: item.batchId ?? item.batch_id ?? "",
        createdAtMs: item.createdAtMs ?? item.created_at_ms ?? 0,
        generatedCount: Number(item.generatedCount ?? item.generated_count ?? 0),
        operatorAdminId: item.operatorAdminId ?? item.operator_admin_id ?? "",
        poolId: item.poolId ?? item.pool_id ?? "",
        requestId: item.requestId ?? item.request_id ?? "",
        requestedCount: Number(item.requestedCount ?? item.requested_count ?? 0),
        ruleConfigJson: item.ruleConfigJson ?? item.rule_config_json ?? "",
        ruleType: item.ruleType ?? item.rule_type ?? "",
        skippedConflictCount: Number(item.skippedConflictCount ?? item.skipped_conflict_count ?? 0),
        status: item.status ?? "",
        updatedAtMs: item.updatedAtMs ?? item.updated_at_ms ?? 0,
    };
}

// 后台发放返回的是新 lease 和用户当前身份快照，identity 归一化后能直接用于测试和后续结果提示。
function normalizeAdminGrantPrettyDisplayIDResult(
    item: RawAdminGrantPrettyDisplayIDResult,
): AdminGrantPrettyDisplayIDResultDto {
    const identity = item.identity || {};
    return {
        identity: {
            defaultDisplayUserId: identity.defaultDisplayUserId ?? identity.default_display_user_id ?? "",
            displayUserId: identity.displayUserId ?? identity.display_user_id ?? "",
            prettyDisplayUserId: identity.prettyDisplayUserId ?? identity.pretty_display_user_id ?? "",
            prettyId: identity.prettyId ?? identity.pretty_id ?? "",
            userId: String(identity.userId ?? identity.user_id ?? ""),
        },
        leaseId: item.leaseId ?? item.lease_id ?? "",
        prettyId: item.prettyId ?? item.pretty_id ?? "",
    };
}

function optionalEntityID(value: unknown): string {
    return nonZeroStringValue(value);
}

function nonZeroStringValue(value: unknown): string {
    const normalized = stringValue(value);
    return normalized === "0" ? "" : normalized;
}
