import { afterEach, expect, test, vi } from "vitest";
import { API_ENDPOINTS } from "@/shared/api/generated/endpoints";
import { setAccessToken } from "@/shared/api/request";
import {
  approveFinanceWithdrawalApplication,
  approveOperationsWithdrawalApplication,
  createFinanceCoinSellerRechargeOrder,
  fetchFinanceOptions,
  fetchFinanceSession,
  fetchFinanceWithdrawalOptions,
  getFinanceCoinSellerRechargeExchangeRate,
  getFinanceUSDTAddresses,
  grantFinanceCoinSellerRechargeOrder,
  listFinanceCoinSellerRechargeOrders,
  listFinanceRechargeDetails,
  listFinanceWithdrawalApplications,
  listOperationsWithdrawalApplications,
  rejectFinanceWithdrawalApplication,
  rejectOperationsWithdrawalApplication,
  quoteFinanceCoinSellerRechargeOrder,
  replaceFinanceCoinSellerRechargeExchangeRate,
  upsertFinanceUSDTAddress,
  verifyFinanceCoinSellerRechargeReceipt,
  verifyFinanceCoinSellerRechargeOrder
} from "./api.js";

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

test("finance session no longer treats removed application permissions as withdrawal access", async () => {
  vi.stubGlobal(
    "fetch",
    vi.fn(async () =>
      new Response(
        JSON.stringify({
          code: 0,
          data: {
            permissions: ["finance-application:audit"],
            user: { account: "legacy-finance" }
          }
        })
      )
    )
  );

  const session = await fetchFinanceSession();

  expect(session.canAccessFinance).toBe(false);
  expect(session.canAuditOperationsWithdrawalApplications).toBe(false);
  expect(session.canAuditWithdrawalApplications).toBe(false);
  expect(session.canViewOperationsWithdrawalApplications).toBe(false);
  expect(session.canViewWithdrawalApplications).toBe(false);
  expect(session).not.toHaveProperty("canAuditApplication");
  expect(session).not.toHaveProperty("canCreateApplication");
});

test("finance options and both withdrawal review stages use generated endpoint paths", async () => {
  vi.stubGlobal(
    "fetch",
    vi.fn(async (url) => {
      if (String(url).includes("/admin/apps")) {
        return new Response(JSON.stringify({
          code: 0,
          data: {
            items: [{ appCode: "lalu", appName: "Lalu", logoUrl: "https://media.example.com/lalu.png" }],
            total: 1
          }
        }));
      }
      if (String(url).includes("/withdrawal-applications?")) {
        return new Response(JSON.stringify({ code: 0, data: { items: [], page: 1, pageSize: 50, total: 0 } }));
      }
      return new Response(JSON.stringify({ code: 0, data: { id: 12 } }));
    })
  );

  const options = await fetchFinanceOptions();
  await listFinanceWithdrawalApplications({ page: 1, page_size: 50 });
  await approveFinanceWithdrawalApplication(14, "lalu", { auditImageUrl: "https://cdn.example.com/pay.png", auditRemark: "ok" });
  await rejectFinanceWithdrawalApplication(15, "aslan", { auditRemark: "bad" });
  await listOperationsWithdrawalApplications({ page: 1, page_size: 50 });
  await approveOperationsWithdrawalApplication(16, "huwaa", { auditRemark: "checked" });
  await rejectOperationsWithdrawalApplication(17, "yumi", { auditRemark: "risk" });

  const calls = vi.mocked(fetch).mock.calls;
  expect(options.apps).toEqual([
    { appCode: "lalu", appName: "Lalu", logoUrl: "https://media.example.com/lalu.png" }
  ]);
  expect(String(calls[0][0])).toContain("/api/v1/admin/apps");
  expect(calls[0][1]?.method).toBe("GET");
  expect(String(calls[1][0])).toContain("/api/v1/admin/finance/withdrawal-applications?");
  expect(calls[1][1]?.method).toBe("GET");
  expect(String(calls[2][0])).toContain("/api/v1/admin/finance/withdrawal-applications/14/approve");
  expect(calls[2][1]?.method).toBe("POST");
  expect(calls[2][1]?.headers?.["X-App-Code"]).toBe("lalu");
  expect(JSON.parse(String(calls[2][1]?.body))).toEqual({ auditImageUrl: "https://cdn.example.com/pay.png", auditRemark: "ok" });
  expect(String(calls[3][0])).toContain("/api/v1/admin/finance/withdrawal-applications/15/reject");
  expect(calls[3][1]?.method).toBe("POST");
  expect(calls[3][1]?.headers?.["X-App-Code"]).toBe("aslan");
  expect(JSON.parse(String(calls[3][1]?.body))).toEqual({ auditRemark: "bad" });
  expect(String(calls[4][0])).toContain("/api/v1/admin/operations/withdrawal-applications?");
  expect(calls[4][1]?.method).toBe("GET");
  expect(String(calls[5][0])).toContain("/api/v1/admin/operations/withdrawal-applications/16/approve");
  expect(calls[5][1]?.method).toBe("POST");
  expect(calls[5][1]?.headers?.["X-App-Code"]).toBe("huwaa");
  expect(JSON.parse(String(calls[5][1]?.body))).toEqual({ auditRemark: "checked" });
  expect(String(calls[6][0])).toContain("/api/v1/admin/operations/withdrawal-applications/17/reject");
  expect(calls[6][1]?.method).toBe("POST");
  expect(calls[6][1]?.headers?.["X-App-Code"]).toBe("yumi");
  expect(JSON.parse(String(calls[6][1]?.body))).toEqual({ auditRemark: "risk" });
});

test("withdrawal options use the MoneyAccess-filtered finance scope catalog", async () => {
  vi.stubGlobal(
    "fetch",
    vi.fn(async () =>
      new Response(
        JSON.stringify({
          code: 0,
          data: {
            all: false,
            apps: [{ appCode: "aslan", appName: "Aslan", logoUrl: "https://media.example.com/aslan.png" }],
            countries: [],
            regions: [],
            scopes: [{ appCode: "aslan", regionId: 7 }]
          }
        })
      )
    )
  );

  const options = await fetchFinanceWithdrawalOptions();
  const [url, init] = vi.mocked(fetch).mock.calls[0];

  expect(String(url)).toContain("/api/v1/admin/finance/scope");
  expect(init?.method).toBe("GET");
  expect(API_ENDPOINTS.getFinanceScope.permissions).toEqual([
    "finance:view",
    "finance-withdrawal:view",
    "finance-withdrawal:audit",
    "operations-withdrawal:view",
    "operations-withdrawal:audit"
  ]);
  expect(options.apps).toEqual([
    { appCode: "aslan", appName: "Aslan", logoUrl: "https://media.example.com/aslan.png" }
  ]);
});

test("operations withdrawal audit permission grants only the operations review queue", async () => {
  vi.stubGlobal(
    "fetch",
    vi.fn(async () =>
      new Response(
        JSON.stringify({
          code: 0,
          data: {
            permissions: ["operations-withdrawal:audit"],
            user: { account: "operations-reviewer" }
          }
        })
      )
    )
  );

  const session = await fetchFinanceSession();

  expect(session.canAccessFinance).toBe(true);
  expect(session.canAuditOperationsWithdrawalApplications).toBe(true);
  expect(session.canViewOperationsWithdrawalApplications).toBe(true);
  expect(session.canAuditWithdrawalApplications).toBe(false);
  expect(session.canViewWithdrawalApplications).toBe(false);
});

test("finance coin seller recharge order APIs use generated endpoint paths", async () => {
  vi.stubGlobal(
    "fetch",
    vi.fn(async (url) => {
      if (String(url).includes("/coin-seller-recharges?")) {
        return new Response(JSON.stringify({ code: 0, data: { items: [], page: 1, pageSize: 50, total: 0 } }));
      }
      return new Response(JSON.stringify({ code: 0, data: { id: "order-1", targetUserId: "10001" } }));
    })
  );

  await listFinanceCoinSellerRechargeOrders({ page: 1, page_size: 50, provider_code: "mifapay" });
  await createFinanceCoinSellerRechargeOrder({
    appCode: "lalu",
    externalOrderNo: "MIFA-001",
    providerAmountMinor: 125050,
    providerCode: "mifapay",
    providerCurrencyCode: "PHP",
    targetUserId: "10001",
    usdAmount: 10
  });
  await verifyFinanceCoinSellerRechargeReceipt({
    appCode: "lalu",
    externalOrderNo: "MIFA-001",
    providerAmountMinor: 125050,
    providerCode: "mifapay",
    providerCurrencyCode: "PHP",
    usdAmount: 10
  });
  await verifyFinanceCoinSellerRechargeOrder("order-1");
  await grantFinanceCoinSellerRechargeOrder("order-1");

  const calls = vi.mocked(fetch).mock.calls;
  expect(String(calls[0][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges?");
  expect(String(calls[0][0])).toContain("provider_code=mifapay");
  expect(calls[0][1]?.method).toBe("GET");
  expect(String(calls[1][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges");
  expect(calls[1][1]?.method).toBe("POST");
  expect(JSON.parse(String(calls[1][1]?.body))).toEqual({
    appCode: "lalu",
    externalOrderNo: "MIFA-001",
    providerAmountMinor: 125050,
    providerCode: "mifapay",
    providerCurrencyCode: "PHP",
    targetUserId: "10001",
    usdAmount: 10
  });
  expect(String(calls[2][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges/receipt-verifications");
  expect(calls[2][1]?.method).toBe("POST");
  expect(JSON.parse(String(calls[2][1]?.body))).toEqual({
    appCode: "lalu",
    externalOrderNo: "MIFA-001",
    providerAmountMinor: 125050,
    providerCode: "mifapay",
    providerCurrencyCode: "PHP",
    usdAmount: 10
  });
  expect(String(calls[3][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges/order-1/verify");
  expect(calls[3][1]?.method).toBe("POST");
  expect(String(calls[4][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges/order-1/grant");
  expect(calls[4][1]?.method).toBe("POST");
});

test("finance coin seller quote and exchange rate APIs use generated endpoint paths", async () => {
  vi.stubGlobal(
    "fetch",
    vi.fn(async () => new Response(JSON.stringify({ code: 0, data: { appCode: "lalu", tiers: [], whitelist: [] } })))
  );

  await quoteFinanceCoinSellerRechargeOrder({ appCode: "lalu", targetUserId: "123456", usdAmount: 150 });
  await getFinanceCoinSellerRechargeExchangeRate("lalu");
  await replaceFinanceCoinSellerRechargeExchangeRate("lalu", {
    tiers: [{ minUsdAmount: 100, maxUsdAmount: 200, coinsPerUsd: 80000 }],
    whitelist: [{ userId: "123456", coinsPerUsd: 100000 }]
  });

  const calls = vi.mocked(fetch).mock.calls;
  expect(String(calls[0][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges/quotes");
  expect(calls[0][1]?.method).toBe("POST");
  expect(JSON.parse(String(calls[0][1]?.body))).toEqual({ appCode: "lalu", targetUserId: "123456", usdAmount: 150 });
  expect(String(calls[1][0])).toContain("/api/v1/admin/finance/coin-seller-recharge-exchange-rates/lalu");
  expect(calls[1][1]?.method).toBe("GET");
  expect(String(calls[2][0])).toContain("/api/v1/admin/finance/coin-seller-recharge-exchange-rates/lalu");
  expect(calls[2][1]?.method).toBe("PUT");
});

test("finance USDT address API uses the selected app path and normalizes SQL rows", async () => {
  vi.stubGlobal(
    "fetch",
    vi.fn(async () =>
      new Response(
        JSON.stringify({
          code: 0,
          data: {
            appCode: "aslan",
            items: [{ address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", chain: "TRON", updatedAtMs: 123 }]
          }
        })
      )
    )
  );

  const result = await getFinanceUSDTAddresses("aslan");

  const [url, init] = vi.mocked(fetch).mock.calls[0];
  expect(String(url)).toContain("/api/v1/admin/finance/usdt-addresses/aslan");
  expect(init?.method).toBe("GET");
  expect(result).toEqual({
    appCode: "aslan",
    items: [{ address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", chain: "TRON", updatedAtMs: 123 }]
  });
});

test("finance USDT address update uses the selected app and chain path", async () => {
  vi.stubGlobal(
    "fetch",
    vi.fn(async () =>
      new Response(JSON.stringify({
        code: 0,
        data: {
          address: "TLv7wERsM42ZeobMrrKVC5C72d3gFUa44J",
          appCode: "aslan",
          chain: "TRON",
          updatedAtMs: 456
        }
      }))
    )
  );

  const result = await upsertFinanceUSDTAddress("aslan", "TRON", { address: "TLv7wERsM42ZeobMrrKVC5C72d3gFUa44J" });
  const [url, init] = vi.mocked(fetch).mock.calls[0];

  expect(String(url)).toContain("/api/v1/admin/finance/usdt-addresses/aslan/TRON");
  expect(init?.method).toBe("PUT");
  expect(JSON.parse(String(init?.body))).toEqual({ address: "TLv7wERsM42ZeobMrrKVC5C72d3gFUa44J" });
  expect(result).toEqual({
    address: "TLv7wERsM42ZeobMrrKVC5C72d3gFUa44J",
    appCode: "aslan",
    chain: "TRON",
    updatedAtMs: 456
  });
});

test("finance recharge details query every app with app header and merge by recharge time", async () => {
  vi.stubGlobal(
    "fetch",
    vi.fn(async (url, init) => {
      const appCode = init?.headers?.["X-App-Code"];
      return new Response(
        JSON.stringify({
          code: 0,
          data: {
            items: [
              {
                appCode,
                coinAmount: appCode === "aslan" ? 2000 : 1000,
                createdAtMs: appCode === "aslan" ? 1760003600000 : 1760000000000,
                exchangeCoinAmount: appCode === "aslan" ? 2000 : 1000,
                exchangeUsdMinorAmount: appCode === "aslan" ? 200 : 100,
                rechargeType: "google_play_recharge",
                status: "succeeded",
                transactionId: `${appCode}-tx`,
                usdMinorAmount: appCode === "aslan" ? 200 : 100,
                userId: "10001"
              }
            ],
            page: 1,
            pageSize: 2,
            total: 1
          }
        })
      );
    })
  );

  const page = await listFinanceRechargeDetails(
    { page: 1, page_size: 2, start_at_ms: 1760000000000, status: "succeeded" },
    [
      { appCode: "lalu", appName: "lalu" },
      { appCode: "aslan", appName: "aslan" }
    ]
  );

  const calls = vi.mocked(fetch).mock.calls;
  expect(page.total).toBe(2);
  expect(page.items.map((item) => item.transactionId)).toEqual(["aslan-tx", "lalu-tx"]);
  expect(calls).toHaveLength(2);
  expect(calls.map(([, init]) => init?.headers?.["X-App-Code"])).toEqual(["lalu", "aslan"]);
  expect(String(calls[0][0])).toContain("/api/v1/admin/payment/recharge-bills?");
  expect(String(calls[0][0])).toContain("page_size=2");
  expect(String(calls[0][0])).toContain("status=succeeded");
  expect(String(calls[0][0])).toContain("start_at_ms=1760000000000");
});
