/* eslint-disable jest/no-conditional-expect */
import xhr from "../xhr";

import {
  FetchException,
  UnauthorizedException,
  TimeoutException,
  NotFoundException,
  JsonParseException,
  IllegalArgumentException,
} from "../../../exceptions";
import xhrMock from "xhr-mock";
import Cache from "../../browser/Cache";

const JSON_TYPE = "application/json";

describe("xhr", () => {
  // replace the real XHR object with the mock XHR object before each test
  // eslint-disable-next-line jest/no-hooks
  beforeEach(() => xhrMock.setup());

  // put the real XHR object back and clear the mocks after each test
  // eslint-disable-next-line jest/no-hooks
  afterEach(() => xhrMock.teardown());

  it("handles simple get requests", async () => {
    expect.assertions(2);

    xhrMock.get("/", (req, res) => {
      expect(req.header("accept")).toBe(JSON_TYPE);
      expect(req.header("Content-Type")).toBe(JSON_TYPE);

      return res.status(200).body({ data: "ok" });
    });

    await xhr({ url: "/" });
  });

  it("should throw when nothing is set or url is missing", () => {
    expect(() => xhr()).toThrow(IllegalArgumentException);
    expect(() => xhr("/")).toThrow(IllegalArgumentException);
    expect(() => xhr({})).toThrow(IllegalArgumentException);
  });

  it("adds encoded params to the url", async () => {
    expect.assertions(2);

    xhrMock.use("get", "/test?param1=one&param2=tw%C3%B6", (req, res) => {
      const { path, query } = req.url();

      expect(path).toBe("/test");
      expect(query).toMatchObject({
        param1: "one",
        param2: "twö",
      });

      return res.status(200);
    });

    await xhr({
      url: "/test",
      params: "param1=one&param2=twö",
    });
  });

  it("adds encoded params to a url with querystring", async () => {
    expect.assertions(2);

    xhrMock.use(
      "get",
      "/test?param1=one&param2=two&param3=thr%C3%A9%C3%AB",
      (req, res) => {
        const { path, query } = req.url();

        expect(path).toBe("/test");
        expect(query).toMatchObject({
          param1: "one",
          param2: "two",
          param3: "thréë",
        });

        return res.status(200).body({});
      },
    );

    await xhr({
      url: "/test?param1=one",
      params: "param2=two&param3=thréë",
    });
  });

  it("handles json response", async () => {
    expect.assertions(1);

    xhrMock.get("/", {
      status: 200,
      body: {
        type: "root",
      },
    });

    await expect(xhr({ url: "/" })).resolves.toStrictEqual({ type: "root" });
  });

  it("return JsonParseException when response is not valid json and responseType is text", async () => {
    expect.assertions(1);

    xhrMock.get("/", {
      status: 200,
      headers: {
        "Content-Type": JSON_TYPE,
      },
      body: "root",
    });

    await expect(() => xhr({ url: "/", responseType: "text" })).rejects.toThrow(
      JsonParseException,
    );
  });

  it("can handle modular ui form responses", async () => {
    expect.assertions(1);

    xhrMock.post("/form", {
      status: 400,
      body: {
        formresponse: {
          form: "response",
        },
      },
    });

    const response = await xhr({ url: "/form", method: "post", data: "{}" });
    expect(response).toStrictEqual({ formresponse: { form: "response" } });
  });

  it("can handle progress events", async () => {
    expect.assertions(1);

    xhrMock.post("/", {
      status: 200,
      headers: {
        "Content-Length": "12",
      },
      body: "Hello world!",
    });

    const progressEvents = new Promise((resolve) => {
      const events = [];

      xhr({
        url: "/",
        method: "post",
        /**
         */
        onProgress: ({ type, loaded, total, lengthComputable }) => {
          events.push({ type, loaded, total, lengthComputable });

          // eslint-disable-next-line jest/no-conditional-in-test
          if (type === "loadend") {
            resolve(events);
          }
        },
        data: "Upload data",
        headers: {
          "Content-Length": "11",
        },
      });
    });

    await expect(progressEvents).resolves.toStrictEqual([
      {
        type: "loadstart",
        lengthComputable: false,
        loaded: 0,
        total: 0,
      },
      {
        type: "progress",
        lengthComputable: true,
        loaded: 12,
        total: 12,
      },
      {
        type: "load",
        lengthComputable: true,
        loaded: 12,
        total: 12,
      },
      {
        type: "loadend",
        lengthComputable: true,
        loaded: 12,
        total: 12,
      },
    ]);
  });

  it("handles error response", async () => {
    expect.assertions(3);
    xhrMock.get("/", {
      status: 500,
      reason: "Internal Server Error",
      body: {
        error: {
          id: "Error.GeneralError",
        },
      },
    });

    await xhr({ url: "/" }).catch((error) => {
      expect(error).toBeInstanceOf(FetchException);
      expect(error.status).toBe(500);
      expect(error.id).toBe("Error.GeneralError");
    });
  });

  it("handles not found response", async () => {
    expect.assertions(3);
    xhrMock.get("/", {
      status: 404,
    });

    await xhr({ url: "/" }).catch((error) => {
      expect(error).toBeInstanceOf(NotFoundException);
      expect(error.status).toBe(404);
      expect(error.id).toBe("NotFoundException");
    });
  });

  it("handles unauthorized response", async () => {
    expect.assertions(3);
    xhrMock.get("/", {
      status: 401,
    });

    await xhr({ url: "/" }).catch((error) => {
      expect(error).toBeInstanceOf(UnauthorizedException);
      expect(error.status).toBe(401);
      expect(error.id).toBe("UnauthorizedException");
    });
  });

  it("handles unauthorized and unexpected response", async () => {
    expect.assertions(1);
    xhrMock.get("/", {
      status: 401,
      body: {
        has: "body?",
      },
    });

    await xhr({ url: "/" }).catch((error) => {
      expect(error).toBeInstanceOf(UnauthorizedException);
    });
  });

  it("handles basic authentication", async () => {
    expect.assertions(1);

    xhrMock.get("/", (req, res) => {
      expect(req.header("Authorization")).toBe("Basic abcdef");
      return res.status(200);
    });

    Cache.addItem("basic", "abcdef");

    await xhr({ url: "/" });
  });

  it("handles timeouts", async () => {
    expect.assertions(2);

    xhrMock.get("/", () => new Promise(() => undefined));

    await xhr({ url: "/", timeout: 2000 }).catch((error) => {
      expect(error).toBeInstanceOf(TimeoutException);
      expect(error.name).toBe("TimeoutException");
    });
  });
});
