import { initial, isInitialized, request, requestPagination } from "./request";

const fetchSuccess = jest.fn(() =>
  Promise.resolve({
    ok: true,
    json: () => Promise.resolve({ data: "test", success: true }),
    headers: new Map(),
  })
);

// 原样返回
const fetchEcho = jest.fn((url: string, options: RequestInit) => {
  return Promise.resolve({
    ok: true,
    json: () =>
      Promise.resolve({
        data: JSON.parse(options.body as string),
        success: true,
      }),
    headers: new Map(),
  });
});

afterEach(() => {
  fetchSuccess.mockClear();
  fetchEcho.mockClear();
});

test("initial", async () => {
  expect(request("test")).rejects.toThrow("请先调用 initial 初始化");

  initial({
    baseURL: "https://example.com",
    fetch: (() => {}) as any,
  });

  expect(isInitialized).toBeTruthy();
});

test("request", async () => {
  const fetch = fetchSuccess;

  initial({
    baseURL: "https://example.com",
    fetch: fetch as any,
  });

  expect(
    await request(
      "/test",
      { foo: "bar" },
      {
        method: "PUT",
        searchParams: { one: "two" },
        headers: {
          "X-POWER-BY": "test",
        },
      }
    )
  ).toEqual("test");

  expect(fetch).toBeCalledWith("https://example.com/test?one=two", {
    body: '{"foo":"bar"}',
    headers: {
      "content-type": "application/json;charset=UTF-8",
      "x-power-by": "test",
    },
    method: "PUT",
    mode: "cors",
  });
});

test("name 参数变量替换", async () => {
  const fetch = fetchSuccess;
  initial({
    baseURL: "https://example.com",
    fetch: fetch as any,
  });

  request("/test/{id}/{foo.bar}", { id: "123", foo: { bar: "baz" } });

  expect(fetch).toBeCalledWith("https://example.com/test/123/baz", {
    body: '{"id":"123","foo":{"bar":"baz"}}',
    headers: { "content-type": "application/json;charset=UTF-8" },
    method: "POST",
    mode: "cors",
  });
});

test("支持识别 application/x-www-form-urlencoded", async () => {
  const fetch = fetchSuccess;
  initial({
    baseURL: "https://example.com",
    fetch: fetch as any,
  });

  expect(
    await request(
      "/test",
      { foo: "bar", baz: 1 },
      {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
      }
    )
  ).toEqual("test");

  expect(fetch).toBeCalledWith("https://example.com/test", {
    body: "baz=1&foo=bar",
    headers: { "content-type": "application/x-www-form-urlencoded" },
    method: "POST",
    mode: "cors",
  });
});

test("拦截器", async () => {
  const fetch = fetchSuccess;

  initial({
    baseURL: "https://example.com",
    fetch: fetch as any,
    interceptor: async (req, next) => {
      req.headers["X-POWER-BY"] = "test";
      req.searchParams.one = "two";
      req.method = "GET";
      req.body.foo = "foo";

      const response = await next();
      response.data = "boom";

      return response;
    },
  });

  expect(await request("/test", { bar: "bar" })).toEqual("boom");
  expect(fetch).toBeCalledWith(
    "https://example.com/test?bar=bar&foo=foo&one=two",
    {
      body: undefined,
      headers: {
        "content-type": "application/json;charset=UTF-8",
        "x-power-by": "test",
      },
      method: "GET",
      mode: "cors",
    }
  );
});

test("拦截器元数据", async () => {
  const fetch = fetchSuccess;
  const interceptor = jest.fn((req, next) => {
    return next();
  });

  initial({
    baseURL: "https://example.com",
    fetch: fetch as any,
    interceptor,
  });

  // @ts-expect-error
  request("/test", {}, { meta: { foo: "bar" } });
  expect(interceptor.mock.calls[0][0]).toEqual({
    body: {},
    headers: { "content-type": "application/json;charset=UTF-8" },
    // 拦截到元数据
    meta: { foo: "bar" },
    method: "POST",
    name: "/test",
    searchParams: {},
  });

  expect(fetch).toBeCalledWith("https://example.com/test", {
    body: "{}",
    headers: { "content-type": "application/json;charset=UTF-8" },
    method: "POST",
    mode: "cors",
  });
});

test("拦截重试, 用于重新登录等复杂场景", async () => {
  const fetch = fetchEcho;
  initial({
    baseURL: "https://example.com",
    fetch: fetch as any,
    interceptor: async (req, next) => {
      req.body.time = 1;
      await next();
      // 二次重试请求
      req.body.time = 2;
      return await next();
    },
  });
  const result = await request("/test");
  // 返回最后一次 next 的请求
  expect(result).toEqual({ time: 2 });
  // fetch 被调用两次
  expect(fetch).toBeCalledWith("https://example.com/test", {
    body: '{"time":1}',
    headers: { "content-type": "application/json;charset=UTF-8" },
    method: "POST",
    mode: "cors",
  });
  expect(fetch).toBeCalledWith("https://example.com/test", {
    body: '{"time":2}',
    headers: { "content-type": "application/json;charset=UTF-8" },
    method: "POST",
    mode: "cors",
  });
});

test("拦截重试，调用 next 多次应该抛出异常", async () => {
  const fetch = fetchSuccess;
  initial({
    baseURL: "https://example.com",
    fetch: fetch as any,
    interceptor: async (req, next) => {
      while (true) {
        await next();
      }
    },
  });

  await expect(request("/test")).rejects.toThrow(
    "拦截器调用 next 次数过多, 请检查代码，可能存在无限循环"
  );
  expect(fetch).toBeCalledTimes(3);
});

test("响应内容规范化 - error", async () => {
  const fetch = jest.fn(() =>
    Promise.resolve({
      ok: true,
      json: () =>
        Promise.resolve({
          data: "test",
          success: false,
          errorCode: 400,
          errorMessage: "mock error",
        }),
      headers: new Map(),
    })
  );

  initial({
    baseURL: "https://example.com",
    fetch: fetch as any,
  });

  try {
    await request("/test");
    // never
    expect(true).toBe(false);
  } catch (err) {
    expect(err).toMatchObject({ code: 400, message: "mock error" });
  }
});

test("响应内容规范化 - success", async () => {
  const fetch = jest.fn(() =>
    Promise.resolve({
      ok: true,
      json: () =>
        Promise.resolve({
          data: "test",
          success: true,
          code: 400,
          msg: "mock error",
          other: "foo",
        }),
      headers: new Map(),
    })
  );

  initial({
    baseURL: "https://example.com",
    fetch: fetch as any,
  });

  expect(await requestPagination("/test")).toEqual({
    __raw__: {
      data: {
        code: 400,
        data: "test",
        msg: "mock error",
        other: "foo",
        success: true,
      },
      header: {},
      statusCode: undefined,
    },
    data: "test",
    code: 400,
    msg: "mock error",
    other: "foo",
    success: true,
  });
});

test("变量替换", async () => {
  const fetch = fetchSuccess;

  initial({
    baseURL: "https://example.com",
    fetch: fetch as any,
    globalVariables: { appId: "mock" },
  });

  expect(
    await request(
      "/test",
      { foo: "bar", myId: "{appId}" },
      {
        method: "PUT",
        searchParams: { one: "two", myId: "{appId}" },
        headers: {
          "X-POWER-BY": "test",
          "MY-ID": "{appId}",
        },
      }
    )
  ).toEqual("test");

  expect(fetch).toBeCalledWith("https://example.com/test?myId=mock&one=two", {
    body: '{"foo":"bar","myId":"mock"}',
    headers: {
      "content-type": "application/json;charset=UTF-8",
      "x-power-by": "test",
      "my-id": "mock",
    },
    method: "PUT",
    mode: "cors",
  });
});
