import { afterEach, describe, expect, test, vi } from "vitest";
import { buildBannerPayload, buildRoomUpdatePayload, createBanner, grantUserVip, listOrganizations, normalizeBannedUser, normalizeOrganization, normalizeUser, unbanUser } from "./business.js";

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

describe("normalizeBannedUser", () => {
  test("uses nested target user id instead of ban log id for unban actions", () => {
    const item = normalizeBannedUser({
      id: "ban-log-9001",
      reason: "spam",
      target: {
        avatar: "https://cdn.test/avatar.png",
        displayUserId: "163000",
        userId: "318705991371722752",
        username: "Fami User"
      }
    });

    expect(item.id).toBe("318705991371722752");
    expect(item.banLogId).toBe("ban-log-9001");
    expect(item.prettyId).toBe("163000");
    expect(item.reason).toBe("spam");
  });

  test("unban request uses the normalized target user id", async () => {
    const user = normalizeBannedUser({ id: "ban-log-1", target: { userId: "canonical-user-2" } });
    const fetchMock = vi.fn(async () => jsonResponse({ code: 0, data: {} }));
    vi.stubGlobal("fetch", fetchMock);

    await unbanUser(user.id, { reason: "manual" });

    expect(fetchMock.mock.calls[0][0]).toBe("/api/v1/external/app/users/canonical-user-2/unban");
  });
});

describe("room API payload", () => {
  test("converts region id to int64-compatible number and maps empty to all regions", () => {
    expect(buildRoomUpdatePayload({ coverUrl: " cover ", description: " desc ", title: " Room ", visibleRegionId: "18" }))
      .toEqual({ coverUrl: "cover", description: "desc", title: "Room", visibleRegionId: 18 });
    expect(buildRoomUpdatePayload({ visibleRegionId: "" }).visibleRegionId).toBe(0);
    expect(() => buildRoomUpdatePayload({ visibleRegionId: "1.5" })).toThrow("errors.regionId");
  });
});

describe("real external DTO normalization", () => {
  test("reads nested levels, vip and ban state from the App user DTO", () => {
    const user = normalizeUser({
      ban: { active: true },
      levels: { charm: { displayLevel: 8 }, wealth: { displayLevel: 12 } },
      userId: "user-1",
      vip: { level: 4 }
    });
    expect(user).toEqual(expect.objectContaining({ banned: true, charmLevel: 8, vipLevel: 4, wealthLevel: 12 }));
  });

  test("normalizes agency owner fields and ordinary member fields", () => {
    expect(normalizeOrganization({
      agencyId: "agency-1",
      name: "Fami Guild",
      ownerAvatar: "owner.png",
      ownerDisplayUserId: "163000",
      ownerUserId: "owner-9",
      ownerUsername: "Guild Owner",
      regionName: "Middle East"
    })).toEqual(expect.objectContaining({
      avatar: "owner.png",
      id: "agency-1",
      name: "Fami Guild",
      prettyId: "163000",
      userId: "owner-9",
      username: "Guild Owner"
    }));
    expect(normalizeOrganization({ displayUserId: "9988", regionName: "SEA", userId: "bd-1", username: "BD One" }))
      .toEqual(expect.objectContaining({ id: "bd-1", name: "BD One", prettyId: "9988" }));
  });

  test("maps BD Manager, Super Admin and team to their real scoped endpoints", async () => {
    const fetchMock = vi.fn(async () => jsonResponse({ code: 0, data: { items: [], total: 0 } }));
    vi.stubGlobal("fetch", fetchMock);

    await listOrganizations("bd-leaders", { page: 1 });
    await listOrganizations("super-admins", { page: 1 });
    await listOrganizations("team", { page: 1 });

    expect(fetchMock.mock.calls.map(([url]) => url)).toEqual([
      "/api/v1/external/admin/managers?page=1",
      "/api/v1/external/admin/bd-leaders?page=1&status=active",
      "/api/v1/external/admin/my-team/agencies?page=1"
    ]);
  });

  test("preserves scoped team totals and truncation metadata", async () => {
    vi.stubGlobal("fetch", vi.fn(async () => jsonResponse({
      code: 0,
      data: {
        agencies: [{ agencyId: "agency-9", ownerUserId: "owner-9", parentBdUserId: "bd-7", status: "active" }],
        total: 1,
        totalBdLeaders: 2,
        totalBds: 7,
        truncated: true
      }
    })));

    const team = await listOrganizations("team", { page: 1 });

    expect(team).toEqual(expect.objectContaining({ total: 1, totalBdLeaders: 2, totalBds: 7, truncated: true }));
    expect(team.items[0]).toEqual(expect.objectContaining({ id: "agency-9", parentBdUserId: "bd-7", userId: "owner-9" }));
  });
});

describe("banner API payload", () => {
  test("uses only the real appconfig banner enums and room image contract", () => {
    expect(buildBannerPayload({
      bannerType: "h5",
      countryCode: "sa",
      coverUrl: "https://cdn.test/banner.webp",
      description: "Campaign",
      displayScopes: ["home", "room"],
      param: "https://fami.test/campaign",
      platform: "android",
      regionId: 8,
      roomSmallImageUrl: "https://cdn.test/room.webp",
      sortOrder: 2,
      status: "active"
    })).toEqual(expect.objectContaining({
      bannerType: "h5",
      countryCode: "SA",
      displayScope: "home",
      displayScopes: ["home", "room"],
      platform: "android",
      roomSmallImageUrl: "https://cdn.test/room.webp",
      status: "active"
    }));
    expect(() => buildBannerPayload({ bannerType: "link", coverUrl: "x", displayScopes: ["home"], platform: "all", status: "enabled" }))
      .toThrow("errors.bannerType");
  });

  test("create API sends the normalized appconfig payload", async () => {
    const fetchMock = vi.fn(async () => jsonResponse({ code: 0, data: {} }));
    vi.stubGlobal("fetch", fetchMock);
    await createBanner({
      bannerType: "app",
      coverUrl: "https://cdn.test/banner.webp",
      displayScopes: ["me"],
      param: JSON.stringify({ type: "wallet" }),
      platform: "ios",
      status: "disabled"
    });

    expect(fetchMock.mock.calls[0][0]).toBe("/api/v1/external/admin/app-config/banners");
    expect(JSON.parse(fetchMock.mock.calls[0][1].body)).toEqual(expect.objectContaining({
      bannerType: "app",
      displayScope: "me",
      displayScopes: ["me"],
      platform: "ios",
      status: "disabled"
    }));
  });
});

describe("VIP grant routing", () => {
  test("routes trial-card VIP grants to vipconfig and never sends track=vip to app users", async () => {
    const fetchMock = vi
      .fn()
      .mockResolvedValueOnce(jsonResponse({ code: 0, data: { config: { grantMode: "trial_card", status: "active" } } }))
      .mockResolvedValueOnce(jsonResponse({ code: 0, data: { levels: [{ level: 5 }] } }))
      .mockResolvedValueOnce(jsonResponse({ code: 0, data: {} }));
    vi.stubGlobal("fetch", fetchMock);

    await grantUserVip("user-9", { commandId: "vip-test", durationMs: 604800000, level: 5, reason: "manual" });

    expect(fetchMock.mock.calls.map(([url]) => url)).toEqual([
      "/api/v1/external/admin/activity/vip-program",
      "/api/v1/external/admin/activity/vip-levels",
      "/api/v1/external/admin/activity/vip-trial-card-grants"
    ]);
    const payload = JSON.parse(fetchMock.mock.calls[2][1].body);
    expect(payload).toEqual(expect.objectContaining({ durationMs: 604800000, level: 5, targetUserId: "user-9" }));
    expect(payload).not.toHaveProperty("track");
  });
});

function jsonResponse(body, status = 200) {
  return new Response(JSON.stringify(body), { headers: { "content-type": "application/json" }, status });
}
