import axios, { AxiosInstance } from "axios";
import AxiosMockAdapter from "axios-mock-adapter";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { TokenManager, createTokenManager } from "../token-manager/token-manager";
import { createAxiosInstance } from "./http-client";
import { BROKER_EVENTS } from "../broker/broker-events";
import { createBroker } from "../broker/factory";

const access_token =
  "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJidFZxNnRMWGpmcXdzbm5MR2FXMXdhdU9McDNiTmY4bWZ3Rm1SZ0lBS2VJIn0.eyJleHAiOjE3NDI5MDkxNjMsImlhdCI6MTc0MjkwODI2MywianRpIjoiODRlMWEwYjYtOGNmYS00NzQ3LTk0MDItMjVkM2FlMzM3N2QxIiwiaXNzIjoiaHR0cHM6Ly9wcmVwcm9kdWNjaW8ucGRzLmhlcy5jYXRzYWx1dC5nZW5jYXQuY2F0L2F1dGgvcmVhbG1zL0hFUyIsInN1YiI6Ijk1OGUyZWQ5LTJkNmUtNDZjOC1hOTE5LTU5MDQ0ZTYwMzVjMiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImV0Yy1jY2YtcHJlIiwic2Vzc2lvbl9zdGF0ZSI6ImVhNzUwMDg4LWI1NjctNDU4ZC1iOWY5LWQ0OTdjMzlkN2ZkMyIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLWhlcyIsIlJPTEVfSEVTX0VUQyIsIlJPTEVfSEVTX0xBTkRJTkciXX0sInNjb3BlIjoiIiwic2lkIjoiZWE3NTAwODgtYjU2Ny00NThkLWI5ZjktZDQ5N2MzOWQ3ZmQzIiwiY2xpZW50SG9zdCI6IjEwLjUzLjI1NC4xNTAiLCJhY2Nlc3NfcnVsZXMiOnt9LCJhY2Nlc3NfaW5mbyI6eyJtb2R1bGVfY29kZSI6IkEwMDEiLCJyb2xlX3R5cGUiOiJOT1JNIiwidHJhY2VfdXNlcl9pZCI6IlVzZXJfSUQiLCJjZW50ZXJfdHlwZSI6IkUiLCJtcGlfcGF0aWVudF9pZCI6IjVlYzc0YzdiLTVhYTUtNGE4NC1iM2I0LWYwMmVkZGZkZjZkYyIsInVwX2NvZGUiOiIwNzczMyIsInRyYWNlX3VzZXJfZ2l2ZW5fbmFtZSI6IkdpdmVuIE5hbWUiLCJ0cmFjZV91c2VyX2ZhbWlseV9uYW1lIjoiRmFtaWx5IE5hbWUiLCJ1c2VyX3R5cGUiOiJBRE0iLCJjZW50ZXJfY29kZSI6IkUwODU4Njk2MyIsInByb2Zlc3Npb25hbF9jYXRlZ29yeSI6IjMwOTM0MzAwNiIsInNlcnZpY2VfY29kZSI6IjVTMDg5IiwiZXBfY29kZSI6IjAyMDgiLCJpZGVudGlmaWVyIjpbeyJ0eXBlIjoiRE5JIiwidmFsdWUiOiI3MzI4ODIxOUEifSx7InR5cGUiOiJOVU1DT0wiLCJ2YWx1ZSI6IjIifV0sImFsdF9pZGVudGlmaWVyIjpbeyJ0eXBlIjoiTVBJIiwidmFsdWUiOiIwNjIxY2Y3ZC03ZDYzLTQ5ZWMtODAwNi04YzA1NjkyZWQzNzcifV19LCJjbGllbnRBZGRyZXNzIjoiMTAuNTMuMjU0LjE1MCIsImNsaWVudF9pZCI6ImV0Yy1jY2YtcHJlIn0.CgYL3zXbUHHiE7ZtrcxvlDHB3dbY_eI8TsL-0mBOy-JedSZZaGT_JS8Odm_eIo1drWZSnEaNblFZUPE56sCEPH6RYSqtyK0eYwy_aKoprKoRuDjEy2-VfmOJi5gY9wpYnAYpVtuTbncVirQIV9SIZ9st2d7LlfcM_88Yn8NJNTDcUk7ETw7Zy_728X2wjL_UeHT7yuzo3Y-Xm_389Og7UMyYGGrs104CHQNrzIMXiklkRZr4dqjqWU61Csrb8OBI6jCUAbBqgS8vPuUzPUghQXstr-aNn3zicy-Zw5e6RqCmSQXg56aTKx9QIOrQuX-az-urPnqvtWdloQrQJLQGSg";
const refresh_token =
  "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIwMjUzZGM1MC1hY2FmLTQ5ZDctYTYzNi0xN2NkMTlmOWEwOTAifQ.eyJleHAiOjE3NDI5MTE4NjMsImlhdCI6MTc0MjkwODI2MywianRpIjoiZjA4MzA1MDktMTA0My00ODI1LTk5YTItOTg1YmNjOGI4MmJmIiwiaXNzIjoiaHR0cHM6Ly9wcmVwcm9kdWNjaW8ucGRzLmhlcy5jYXRzYWx1dC5nZW5jYXQuY2F0L2F1dGgvcmVhbG1zL0hFUyIsImF1ZCI6Imh0dHBzOi8vcHJlcHJvZHVjY2lvLnBkcy5oZXMuY2F0c2FsdXQuZ2VuY2F0LmNhdC9hdXRoL3JlYWxtcy9IRVMiLCJzdWIiOiI5NThlMmVkOS0yZDZlLTQ2YzgtYTkxOS01OTA0NGU2MDM1YzIiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiZXRjLWNjZi1wcmUiLCJzZXNzaW9uX3N0YXRlIjoiZWE3NTAwODgtYjU2Ny00NThkLWI5ZjktZDQ5N2MzOWQ3ZmQzIiwic2NvcGUiOiIiLCJzaWQiOiJlYTc1MDA4OC1iNTY3LTQ1OGQtYjlmOS1kNDk3YzM5ZDdmZDMifQ.I8u83nfdxbjRrS7dC1k8_f9oBKmSD_Xu4iAxlFqcA3QdHi-L_yY63ENU1LTehh8-2d51f__nv58zPb799hNGhg";
const newAccessToken =
  "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJidFZxNnRMWGpmcXdzbm5MR2FXMXdhdU9McDNiTmY4bWZ3Rm1SZ0lBS2VJIn0.eyJleHAiOjE3NDI5MTAzNzgsImlhdCI6MTc0MjkwOTQ3OCwianRpIjoiMTg4OTE3ZDYtNmM0OS00YWY0LTlhYjItZjEzYTM4MGY5OTgyIiwiaXNzIjoiaHR0cHM6Ly9wcmVwcm9kdWNjaW8ucGRzLmhlcy5jYXRzYWx1dC5nZW5jYXQuY2F0L2F1dGgvcmVhbG1zL0hFUyIsInN1YiI6Ijk1OGUyZWQ5LTJkNmUtNDZjOC1hOTE5LTU5MDQ0ZTYwMzVjMiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImV0Yy1jY2YtcHJlIiwic2Vzc2lvbl9zdGF0ZSI6IjY5ODdhMzExLWM1NzQtNDc1Ni1hMzY0LWY1ZjQzZmViZjNhYiIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLWhlcyIsIlJPTEVfSEVTX0VUQyIsIlJPTEVfSEVTX0xBTkRJTkciXX0sInNjb3BlIjoiIiwic2lkIjoiNjk4N2EzMTEtYzU3NC00NzU2LWEzNjQtZjVmNDNmZWJmM2FiIiwiY2xpZW50SG9zdCI6IjEwLjUzLjI1NC4xNTAiLCJhY2Nlc3NfcnVsZXMiOnt9LCJhY2Nlc3NfaW5mbyI6eyJtb2R1bGVfY29kZSI6IkEwMDEiLCJyb2xlX3R5cGUiOiJOT1JNIiwidHJhY2VfdXNlcl9pZCI6IlVzZXJfSUQiLCJjZW50ZXJfdHlwZSI6IkUiLCJtcGlfcGF0aWVudF9pZCI6IjVlYzc0YzdiLTVhYTUtNGE4NC1iM2I0LWYwMmVkZGZkZjZkYyIsInVwX2NvZGUiOiIwNzczMyIsInRyYWNlX3VzZXJfZ2l2ZW5fbmFtZSI6IkdpdmVuIE5hbWUiLCJ0cmFjZV91c2VyX2ZhbWlseV9uYW1lIjoiRmFtaWx5IE5hbWUiLCJ1c2VyX3R5cGUiOiJBRE0iLCJjZW50ZXJfY29kZSI6IkUwODU4Njk2MyIsInByb2Zlc3Npb25hbF9jYXRlZ29yeSI6IjMwOTM0MzAwNiIsInNlcnZpY2VfY29kZSI6IjVTMDg5IiwiZXBfY29kZSI6IjAyMDgiLCJpZGVudGlmaWVyIjpbeyJ0eXBlIjoiRE5JIiwidmFsdWUiOiI3MzI4ODIxOUEifSx7InR5cGUiOiJOVU1DT0wiLCJ2YWx1ZSI6IjIifV0sImFsdF9pZGVudGlmaWVyIjpbeyJ0eXBlIjoiTVBJIiwidmFsdWUiOiIwNjIxY2Y3ZC03ZDYzLTQ5ZWMtODAwNi04YzA1NjkyZWQzNzcifV19LCJjbGllbnRBZGRyZXNzIjoiMTAuNTMuMjU0LjE1MCIsImNsaWVudF9pZCI6ImV0Yy1jY2YtcHJlIn0.zPxoN5d8_uxdbxeH7ly9sQdlnDxNp8_eWDzNSzjzkUPsRVkLxgjQw_WcjZfns-LNgGr0FToS6uSDIC7uj2hPpKqD7HPWbI6UPa7g-43Jf9rh-KSOXEHJWhlASi2w0n3xQHFSxHDYBwhyXlvWQ_Tr1Vh37MroWmEL84fdwqhcfZHmD9Ek52EuRRrB-XkymCmN1ZvO2GPU04LbBvQxLVzdAaKRzr1cUqHTnsEdgRduyKShPcyth01L_X7m128SIwjJsa1lYxFE_DbSDKz8dPTsxoS1DgwRxeArrkMlIdTHyxmDRezZUGhamyLuWpXLDXJCFfrTUB8-j_JnVlILHeR6O";

describe("HTTP Client", () => {
  let broker = createBroker();
  let tokenManager: TokenManager;
  let axiosInstance: AxiosInstance;
  let axiosMockInstance: AxiosMockAdapter;

  beforeEach(() => {
    vi.restoreAllMocks(); // Reset all spies before each test
    vi.spyOn(window, "location", "get").mockReturnValue({
      search: `?access_token=${access_token}&refresh_token=${refresh_token}`,
    } as unknown as Location);
  });

  describe("Without validateMpid", () => {
    beforeEach(() => {
      tokenManager = createTokenManager(broker);
      axiosInstance = createAxiosInstance(tokenManager, broker);
      axiosMockInstance = new AxiosMockAdapter(axiosInstance);
      axiosMockInstance.reset();
    });

    it("should make a request with header", async () => {
      axiosMockInstance.onGet("/api/clinical-course").reply(200, "success");
      // Perform the request
      const response = await axiosInstance.get("/api/clinical-course");
      // Assertions
      expect(response.status).toBe(200);
      expect(response.data).toBe("success");
      expect(axiosMockInstance.history.get?.length).toBe(1);
      // @ts-ignore
      expect(axiosMockInstance.history.get?.[0]?.headers.Authorization).toBe(
        `Bearer ${access_token}`,
      );
    });

    it("should retry request with  new token if failed with 401 after token refresh, and requests after that have new token", async () => {
      axiosMockInstance
        .onGet("/api/clinical-course")
        .replyOnce(401, "Unauthorized")
        .onGet("/api/clinical-course")
        .replyOnce(200, "success")
        .onGet("/api/clinical-course/2")
        .replyOnce(200, "success");

      vi.spyOn(axios, "post").mockResolvedValueOnce({
        data: {
          access_token: newAccessToken,
          refresh_token: "new-refresh-token",
        },
      });

      // Perform the request
      const response = await axiosInstance.get("/api/clinical-course");
      const secondResponse = await axiosInstance.get("/api/clinical-course/2");
      // Assertions
      expect(response.status).toBe(200);
      expect(response.data).toBe("success");
      expect(axiosMockInstance.history.get?.length).toBe(3); // 3 Calls 2 for the first request (request and retry), 1 for the second request
      expect(axiosMockInstance.history.get?.[1]?.headers?.Authorization).toBe(
        "Bearer " + newAccessToken,
      );
      expect(axiosMockInstance.history.get?.[2]?.headers?.Authorization).toBe(
        "Bearer " + newAccessToken,
      );
    });

    it("should fail request if token refresh fails and publish an event", async () => {
      vi.spyOn(axios, "post").mockRejectedValueOnce({
        response: { status: 400, statusText: "Bad Request" },
      });
      const brokerSpy = vi.spyOn(broker, "publish");
      axiosMockInstance.onGet("/api/clinical-course").replyOnce(401, "Unauthorized");

      await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
        "Request failed with status code 401",
      );

      expect(axiosMockInstance.history.get?.length).toBe(1); // No retry happened
      expect(brokerSpy).toHaveBeenCalledWith(BROKER_EVENTS.shell.refreshTokenFailed, {});
    });

    it("should fail request with error != 401 and should not retry", async () => {
      const refreshTokenSpy = vi.spyOn(tokenManager, "refreshToken");
      const brokerSpy = vi.spyOn(broker, "publish");
      axiosMockInstance.onGet("/api/clinical-course").replyOnce(500, "Internal Server Error");
      await expect(axiosInstance.get("/api/clinical-course")).rejects.toMatchObject({
        response: { status: 500 },
      });
      expect(axiosMockInstance.history.get?.length).toBe(1);
      expect(refreshTokenSpy).not.toHaveBeenCalled();
      expect(brokerSpy).not.toHaveBeenCalled();
    });
  });

  describe.skip("With validateMpid", () => {
    const validTokenHeaders = { "x-catsalut-mpid": "5ec74c7b-5aa5-4a84-b3b4-f02eddfdf6dc" };
    beforeEach(() => {
      tokenManager = createTokenManager(broker);
      axiosInstance = createAxiosInstance(tokenManager, broker, true);
      axiosMockInstance = new AxiosMockAdapter(axiosInstance);
      axiosMockInstance.reset();
    });

    it("should throw error if mpid header is missing without sending broker event", async () => {
      axiosMockInstance.onGet("/api/clinical-course").reply(200, "success");
      const brokerSpy = vi.spyOn(broker, "publish");

      // Perform the request
      await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
        "Mpid header is missing",
      );
      expect(brokerSpy).not.toHaveBeenCalled();
    });

    it("should throw error if mpid header value is invalid and send broker event ", async () => {
      axiosMockInstance.onGet("/api/clinical-course").reply(200, "success", {
        "x-catsalut-mpid": "invalid-mpid",
      });
      const brokerSpy = vi.spyOn(broker, "publish");
      // Perform the request
      await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
        "Mpid header value is invalid",
      );
      expect(brokerSpy).toHaveBeenCalledWith(BROKER_EVENTS.shell.mpidHeaderInvalid, expect.any(Object));
    });

    it("should make a request with header and respond succesful if x-catsalut-mpid has valid value ", async () => {
      axiosMockInstance.onGet("/api/clinical-course").reply(200, "success", validTokenHeaders);
      // Perform the request
      const response = await axiosInstance.get("/api/clinical-course");
      // Assertions
      expect(response.status).toBe(200);
      expect(response.data).toBe("success");
      expect(axiosMockInstance.history.get?.length).toBe(1);
      // @ts-ignore
      expect(axiosMockInstance.history.get?.[0]?.headers.Authorization).toBe(
        `Bearer ${access_token}`,
      );
    });

    it("should retry request with  new token if failed with 401 after token refresh, and requests after that have new token", async () => {
      axiosMockInstance
        .onGet("/api/clinical-course")
        .replyOnce(401, "Unauthorized")
        .onGet("/api/clinical-course")
        .replyOnce(200, "success", validTokenHeaders)
        .onGet("/api/clinical-course/2")
        .replyOnce(200, "success", validTokenHeaders);

      vi.spyOn(axios, "post").mockResolvedValueOnce({
        data: {
          access_token: newAccessToken,
          refresh_token: "new-refresh-token",
        },
      });

      // Perform the request
      const response = await axiosInstance.get("/api/clinical-course");
      const secondResponse = await axiosInstance.get("/api/clinical-course/2");

      // Assertions
      expect(response.status).toBe(200);
      expect(response.data).toBe("success");
      expect(axiosMockInstance.history.get?.length).toBe(3); // 3 Calls 2 for the first request (request and retry), 1 for the second request
      expect(axiosMockInstance.history.get?.[1]?.headers?.Authorization).toBe(
        "Bearer " + newAccessToken,
      );
      expect(axiosMockInstance.history.get?.[2]?.headers?.Authorization).toBe(
        "Bearer " + newAccessToken,
      );
    });

    it("should fail request if token refresh fails and publish an event", async () => {
      vi.spyOn(axios, "post").mockRejectedValueOnce({
        response: { status: 400, statusText: "Bad Request" },
      });
      const brokerSpy = vi.spyOn(broker, "publish");
      axiosMockInstance.onGet("/api/clinical-course").replyOnce(401, "Unauthorized");

      await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
        "Request failed with status code 401",
      );

      expect(axiosMockInstance.history.get?.length).toBe(1); // No retry happened
      expect(brokerSpy).toHaveBeenCalledWith(BROKER_EVENTS.shell.refreshTokenFailed, {});
    });

    it("should fail request with error != 401 and should not retry", async () => {
      const refreshTokenSpy = vi.spyOn(tokenManager, "refreshToken");
      const brokerSpy = vi.spyOn(broker, "publish");
      axiosMockInstance.onGet("/api/clinical-course").replyOnce(500, "Internal Server Error");
      await expect(axiosInstance.get("/api/clinical-course")).rejects.toMatchObject({
        response: { status: 500 },
      });
      expect(axiosMockInstance.history.get?.length).toBe(1);
      expect(refreshTokenSpy).not.toHaveBeenCalled();
      expect(brokerSpy).not.toHaveBeenCalled();
    });
  });
});
