import { afterEach, expect, test, vi } from "vitest";
import { setAccessToken } from "@/shared/api/request";
import {
    adjustAppUserLevels,
    banAppUser,
    createAppUserExport,
    getAppUser,
    issueAppUserAccessToken,
    listAppUsers,
    listPrettyDisplayIDs,
    recyclePrettyDisplayID,
    revokeAppUserResource,
    unbanAppUser,
} from "./api";

afterEach(() => {
    setAccessToken("");
    vi.unstubAllGlobals();
});

test("listPrettyDisplayIDs uses admin endpoint and normalizes snake case fields", async () => {
    vi.stubGlobal(
        "fetch",
        vi.fn(
            async () =>
                new Response(
                    JSON.stringify({
                        code: 0,
                        data: {
                            items: [
                                {
                                    assigned_user_id: "10001",
                                    display_user_id: "VIP2026",
                                    pool: {
                                        level_track: "wealth",
                                        max_level: 10,
                                        min_level: 1,
                                        name: "财富池",
                                        pool_id: "pool-1",
                                        rule_type: "aabbcc",
                                        status: "active",
                                    },
                                    pool_id: "pool-1",
                                    pretty_id: "pretty-1",
                                    source: "pool",
                                    status: "assigned",
                                },
                            ],
                            page: 1,
                            page_size: 20,
                            total: 1,
                        },
                    }),
                ),
        ),
    );

    const result = await listPrettyDisplayIDs({ keyword: "VIP", page: 1, page_size: 20, status: "assigned" });
    const [url, init] = vi.mocked(fetch).mock.calls[0];

    expect(String(url)).toContain("/api/v1/admin/users/pretty-ids?");
    expect(String(url)).toContain("keyword=VIP");
    expect(String(url)).toContain("status=assigned");
    expect(init?.method).toBe("GET");
    expect(result.pageSize).toBe(20);
    expect(result.items[0]).toMatchObject({
        assignedUserId: "10001",
        displayUserId: "VIP2026",
        pool: { levelTrack: "wealth", poolId: "pool-1" },
        prettyId: "pretty-1",
    });
});

test("listPrettyDisplayIDs normalizes assigned user and operator snapshots", async () => {
    vi.stubGlobal(
        "fetch",
        vi.fn(
            async () =>
                new Response(
                    JSON.stringify({
                        code: 0,
                        data: {
                            items: [
                                {
                                    assigned_user: {
                                        avatar: "https://cdn.example/u.png",
                                        default_display_user_id: "10001",
                                        display_user_id: "777777",
                                        user_id: "9001",
                                        username: "pretty owner",
                                    },
                                    assigned_user_id: "9001",
                                    created_by_admin_id: "7",
                                    display_user_id: "777777",
                                    operator: {
                                        account: "ops",
                                        admin_id: "8",
                                        name: "运营",
                                        type: "admin",
                                    },
                                    pretty_id: "pretty-2",
                                    source: "admin_grant",
                                    status: "assigned",
                                    updated_by_admin_id: "8",
                                },
                            ],
                            page: 1,
                            page_size: 20,
                            total: 1,
                        },
                    }),
                ),
        ),
    );

    const result = await listPrettyDisplayIDs({ page: 1, page_size: 20 });

    expect(result.items[0]).toMatchObject({
        assignedUser: {
            avatar: "https://cdn.example/u.png",
            defaultDisplayUserId: "10001",
            displayUserId: "777777",
            userId: "9001",
            username: "pretty owner",
        },
        operator: {
            account: "ops",
            adminId: "8",
            name: "运营",
            type: "admin",
        },
        updatedByAdminId: "8",
    });
});

test("recyclePrettyDisplayID uses generated recycle endpoint", async () => {
    vi.stubGlobal(
        "fetch",
        vi.fn(
            async () =>
                new Response(
                    JSON.stringify({
                        code: 0,
                        data: {
                            assigned_user_id: "",
                            display_user_id: "777777",
                            pretty_id: "pretty-2",
                            released_at_ms: 1782493200000,
                            source: "admin_grant",
                            status: "released",
                        },
                    }),
                ),
        ),
    );

    const result = await recyclePrettyDisplayID("pretty-2");
    const [url, init] = vi.mocked(fetch).mock.calls[0];

    expect(String(url)).toContain("/api/v1/admin/users/pretty-ids/pretty-2/recycle");
    expect(init?.method).toBe("POST");
    expect(result).toMatchObject({
        assignedUserId: "",
        displayUserId: "777777",
        prettyId: "pretty-2",
        status: "released",
    });
});

test("listPrettyDisplayIDs normalizes zero assigned user as empty", async () => {
    vi.stubGlobal(
        "fetch",
        vi.fn(
            async () =>
                new Response(
                    JSON.stringify({
                        code: 0,
                        data: {
                            items: [
                                {
                                    assigned_user_id: "0",
                                    display_user_id: "00002",
                                    pretty_id: "pretty-0",
                                    source: "pool",
                                    status: "available",
                                },
                            ],
                            page: 1,
                            page_size: 20,
                            total: 1,
                        },
                    }),
                ),
        ),
    );

    const result = await listPrettyDisplayIDs({ page: 1, page_size: 20 });

    expect(result.items[0]).toMatchObject({
        assignedUser: undefined,
        assignedUserId: "",
        displayUserId: "00002",
    });
});

test("listAppUsers sends country region and time filters", async () => {
    vi.stubGlobal(
        "fetch",
        vi.fn(
            async () => new Response(JSON.stringify({ code: 0, data: { items: [], page: 1, pageSize: 50, total: 0 } })),
        ),
    );

    const result = await listAppUsers({
        country: "PH",
        end_ms: 2000,
        page: 1,
        page_size: 50,
        region_id: 3,
        start_ms: 1000,
    });
    const [url, init] = vi.mocked(fetch).mock.calls[0];

    expect(result.items).toEqual([]);
    expect(String(url)).toContain("/api/v1/app/users?");
    expect(String(url)).toContain("country=PH");
    expect(String(url)).toContain("region_id=3");
    expect(String(url)).toContain("start_ms=1000");
    expect(String(url)).toContain("end_ms=2000");
    expect(init?.method).toBe("GET");
});

test("listAppUsers normalizes pretty display id fields from snake case", async () => {
    vi.stubGlobal(
        "fetch",
        vi.fn(
            async () =>
                new Response(
                    JSON.stringify({
                        code: 0,
                        data: {
                            items: [
                                {
                                    avatar: "https://cdn.example/avatar.png",
                                    coin: 99,
                                    default_display_user_id: "123456",
                                    display_user_id: "123456",
                                    pretty_display_user_id: "VIP2026",
                                    pretty_id: "pretty-2026",
                                    user_id: "10001",
                                    username: "tester",
                                    vip: {
                                        active: true,
                                        expires_at_ms: 1790000000000,
                                        level: 3,
                                        name: "黄金会员",
                                    },
                                },
                            ],
                            page: 1,
                            page_size: 50,
                            total: 1,
                        },
                    }),
                ),
        ),
    );

    const result = await listAppUsers({ page: 1, page_size: 50 });

    expect(result.pageSize).toBe(50);
    expect(result.items[0]).toMatchObject({
        defaultDisplayUserId: "123456",
        displayUserId: "123456",
        prettyDisplayUserId: "VIP2026",
        prettyId: "pretty-2026",
        userId: "10001",
        username: "tester",
        vip: expect.objectContaining({
            active: true,
            level: 3,
            name: "黄金会员",
        }),
    });
});

test("getAppUser uses detail endpoint and normalizes assets", async () => {
    vi.stubGlobal(
        "fetch",
        vi.fn(
            async () =>
                new Response(
                    JSON.stringify({
                        code: 0,
                        data: {
                            display_user_id: "123456",
                            default_display_user_id: "123456",
                            pretty_display_user_id: "VIP888",
                            pretty_id: "pretty-888",
                            user_id: "10001",
                            balances: [
                                {
                                    asset_type: "COIN",
                                    available_amount: 1200,
                                    frozen_amount: 50,
                                    total_amount: 1250,
                                    version: 7,
                                },
                            ],
                            equipped_resources: [
                                {
                                    entitlement_id: "ent-1",
                                    equipped: true,
                                    name: "金色头像框",
                                    quantity: 1,
                                    remaining_quantity: 1,
                                    resource_code: "frame_gold",
                                    resource_id: 9,
                                    resource_type: "avatar_frame",
                                },
                            ],
                            resources: [
                                {
                                    entitlement_id: "ent-1",
                                    equipped: true,
                                    name: "金色头像框",
                                    quantity: 1,
                                    remaining_quantity: 1,
                                    resource_code: "frame_gold",
                                    resource_id: 9,
                                    resource_type: "avatar_frame",
                                },
                            ],
                        },
                    }),
                ),
        ),
    );

    const result = await getAppUser("10001");
    const [url, init] = vi.mocked(fetch).mock.calls[0];

    expect(String(url)).toContain("/api/v1/app/users/10001");
    expect(init?.method).toBe("GET");
    expect(result.defaultDisplayUserId).toBe("123456");
    expect(result.prettyDisplayUserId).toBe("VIP888");
    expect(result.prettyId).toBe("pretty-888");
    expect(result.balances?.[0]).toMatchObject({
        assetType: "COIN",
        availableAmount: 1200,
        frozenAmount: 50,
        totalAmount: 1250,
        version: 7,
    });
    expect(result.equippedResources?.[0]).toMatchObject({
        equipped: true,
        name: "金色头像框",
        resourceType: "avatar_frame",
    });
    expect(result.resources?.[0]).toMatchObject({
        entitlementId: "ent-1",
        resourceCode: "frame_gold",
    });
});

test("revokeAppUserResource uses the precise entitlement endpoint", async () => {
    vi.stubGlobal(
        "fetch",
        vi.fn(
            async () =>
                new Response(
                    JSON.stringify({
                        code: 0,
                        data: {
                            entitlementId: "ent-host-badge",
                            equipped: false,
                            remainingQuantity: 0,
                            resourceId: 91,
                            status: "revoked",
                        },
                    }),
                ),
        ),
    );

    await revokeAppUserResource("318705991371722752", "ent-host-badge", {
        reason: "后台用户详情撤回素材：host",
    });
    const [url, init] = vi.mocked(fetch).mock.calls[0];

    expect(String(url)).toContain(
        "/api/v1/admin/users/318705991371722752/resources/ent-host-badge/revoke",
    );
    expect(init?.method).toBe("POST");
    expect(JSON.parse(String(init?.body))).toEqual({ reason: "后台用户详情撤回素材：host" });
});

test("app user projection normalizes levels roles ban and successful login fields", async () => {
    vi.stubGlobal(
        "fetch",
        vi.fn(
            async () =>
                new Response(
                    JSON.stringify({
                        code: 0,
                        data: {
                            age: 26,
                            app_version: "3.2.1",
                            app_version_at_ms: 1779990000000,
                            ban: {
                                active: true,
                                expires_at_ms: 1790000000000,
                                id: "ban-1",
                                permanent: false,
                                reason: "risk",
                                source: "admin",
                            },
                            birth: "2000-02-29",
                            build_number: "345",
                            cumulative_recharge_usd_minor: 12345,
                            devices: [
                                {
                                    app_version: "3.2.1",
                                    build_number: "345",
                                    device_id: "android_abcdef",
                                    device_model: "Redmi Note 8 Pro",
                                    first_seen_at_ms: 1770000000000,
                                    imei: "",
                                    last_seen_at_ms: 1781500000000,
                                    manufacturer: "Xiaomi",
                                    os_version: "13",
                                    platform: "android",
                                },
                            ],
                            last_login_at_ms: 1780000000000,
                            last_login_ip: "203.0.113.7",
                            last_login_ip_at_ms: 1781600000000,
                            last_login_ip_country_code: "AE",
                            last_operated_at_ms: 1781000000000,
                            last_operator: { action: "ban", admin_id: "7", name: "运营" },
                            levels: {
                                wealth: {
                                    display_level: 20,
                                    expires_at_ms: 1790000000000,
                                    real_level: 8,
                                    temporary_level_id: "temporary-1",
                                    temporary_target_level: 20,
                                    track: "wealth",
                                },
                            },
                            register_device: "iPhone17,2",
                            register_device_id: "ios_1F2E3D",
                            register_os_version: "17.5",
                            roles: ["主播", "BD"],
                            third_party_identities: [
                                {
                                    created_at_ms: 1760000000000,
                                    openid: "g-openid-1",
                                    provider: "google",
                                    union_id: "g-union-1",
                                },
                            ],
                            user_id: "10001",
                        },
                    }),
                ),
        ),
    );

    const result = await getAppUser("10001");

    expect(result).toMatchObject({
        age: 26,
        appVersion: "3.2.1",
        appVersionAtMs: 1779990000000,
        ban: { active: true, expiresAtMs: 1790000000000, id: "ban-1", permanent: false },
        birth: "2000-02-29",
        buildNumber: "345",
        cumulativeRechargeUsdMinor: 12345,
        devices: [
            {
                appVersion: "3.2.1",
                buildNumber: "345",
                deviceId: "android_abcdef",
                deviceModel: "Redmi Note 8 Pro",
                firstSeenAtMs: 1770000000000,
                imei: "",
                lastSeenAtMs: 1781500000000,
                manufacturer: "Xiaomi",
                osVersion: "13",
                platform: "android",
            },
        ],
        lastLoginAtMs: 1780000000000,
        lastLoginIp: "203.0.113.7",
        lastLoginIpAtMs: 1781600000000,
        lastLoginIpCountryCode: "AE",
        lastOperatedAtMs: 1781000000000,
        lastOperator: { action: "ban", adminId: "7", name: "运营" },
        levels: {
            wealth: expect.objectContaining({
                displayLevel: 20,
                realLevel: 8,
                temporaryLevelId: "temporary-1",
                temporaryTargetLevel: 20,
            }),
        },
        registerDevice: "iPhone17,2",
        registerDeviceId: "ios_1F2E3D",
        registerOsVersion: "17.5",
        roles: ["主播", "BD"],
        thirdPartyIdentities: [
            { createdAtMs: 1760000000000, openid: "g-openid-1", provider: "google", unionId: "g-union-1" },
        ],
    });
});

test("issueAppUserAccessToken posts to access-token endpoint and normalizes the token payload", async () => {
    vi.stubGlobal(
        "fetch",
        vi.fn(
            async () =>
                new Response(
                    JSON.stringify({
                        code: 0,
                        data: {
                            access_token: "eyJhbGciOiJIUzI1NiJ9.payload.signature",
                            device_id: "android_abcdef",
                            expires_in_sec: 7200,
                            session_id: "session-1",
                            session_last_heartbeat_at_ms: 1781700000000,
                            token_type: "Bearer",
                        },
                    }),
                ),
        ),
    );

    const result = await issueAppUserAccessToken("10001");
    const [url, init] = vi.mocked(fetch).mock.calls[0];

    expect(String(url)).toContain("/api/v1/app/users/10001/access-token");
    expect(init?.method).toBe("POST");
    expect(result).toEqual({
        accessToken: "eyJhbGciOiJIUzI1NiJ9.payload.signature",
        deviceId: "android_abcdef",
        expiresInSec: 7200,
        sessionId: "session-1",
        sessionLastHeartbeatAtMs: 1781700000000,
        tokenType: "Bearer",
    });
});

test("temporary level adjustment sends wealth and charm in one request", async () => {
    vi.stubGlobal(
        "fetch",
        vi.fn(async () => new Response(JSON.stringify({ code: 0, data: { user_id: "10001" } }))),
    );
    const payload = {
        adjustments: [
            { duration_days: 7, level: 20, track: "wealth" as const },
            { duration_days: 3, level: 18, track: "charm" as const },
        ],
        reason: "campaign",
    };

    const result = await adjustAppUserLevels("10001", payload);
    const [url, init] = vi.mocked(fetch).mock.calls[0];

    expect(String(url)).toContain("/api/v1/app/users/10001/levels");
    expect(init?.method).toBe("PUT");
    expect(JSON.parse(String(init?.body))).toEqual(payload);
    expect(result.userId).toBe("10001");
});

test("ban and unban normalize the status mutation user wrapper", async () => {
    const fetchMock = vi
        .fn()
        .mockResolvedValueOnce(
            new Response(
                JSON.stringify({
                    code: 0,
                    data: {
                        user: {
                            ban: { active: true, expires_at_ms: 1790000000000, permanent: false },
                            status: "banned",
                            user_id: "10001",
                        },
                    },
                }),
            ),
        )
        .mockResolvedValueOnce(
            new Response(
                JSON.stringify({ code: 0, data: { user: { ban: { active: false }, status: "active", user_id: "10001" } } }),
            ),
        );
    vi.stubGlobal("fetch", fetchMock);

    const banned = await banAppUser("10001", { expires_at_ms: 1790000000000, reason: "risk" });
    const unbanned = await unbanAppUser("10001", { reason: "cleared" });

    expect(banned).toMatchObject({ ban: { active: true, expiresAtMs: 1790000000000 }, status: "banned" });
    expect(unbanned).toMatchObject({ ban: { active: false }, status: "active" });
    expect(JSON.parse(String(fetchMock.mock.calls[0][1]?.body))).toEqual({
        expires_at_ms: 1790000000000,
        reason: "risk",
    });
    expect(JSON.parse(String(fetchMock.mock.calls[1][1]?.body))).toEqual({ reason: "cleared" });
});

test("app user export creates a task from the current filter snapshot", async () => {
    vi.stubGlobal(
        "fetch",
        vi.fn(
            async () =>
                new Response(
                    JSON.stringify({ code: 0, data: { jobId: 77, snapshotAtMs: 1781000000000, status: "pending" } }),
                ),
        ),
    );

    const result = await createAppUserExport({ country: "AE", status: "banned" });
    const [url, init] = vi.mocked(fetch).mock.calls[0];

    expect(String(url)).toContain("/api/v1/exports/app-users?");
    expect(String(url)).toContain("country=AE");
    expect(String(url)).toContain("status=banned");
    expect(init?.method).toBe("POST");
    expect(result).toEqual({ jobId: 77, snapshotAtMs: 1781000000000, status: "pending" });
});
