import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
import { TokenManager } from "../token-manager/token-manager";
import { BROKER_EVENTS } from "../broker/broker-events";
import { PrimariaBroker } from "../broker/primaria-broker";
import { jwtDecode } from "jwt-decode";

const mpidHeader = "x-catsalut-mpid";
const obtainMpid = (token: string) => {
  const mpid = (jwtDecode(token) as any).access_info?.mpi_patient_id;
  return mpid;
};

export class MissingMpidHeaderError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "MissingMpidHeaderError";
  }
}

export class InvalidMpidHeaderError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "InvalidMpidHeaderError";
  }
}
const validateMpidHeader = (response: AxiosResponse, tokenManager: TokenManager): boolean => {
  const mpidHeaderValue = response.headers[mpidHeader];
  if (!mpidHeaderValue) {
    throw new MissingMpidHeaderError("Mpid header is missing");
  }
  if (mpidHeaderValue !== obtainMpid(tokenManager.getToken())) {
    throw new InvalidMpidHeaderError("Mpid header value is invalid");
  }
  return true;
};

export const createAxiosInstance = (
  tokenManager: TokenManager,
  broker: PrimariaBroker,
  validateMpid: boolean = false,
) => {
  const instance = axios.create();
  instance.interceptors.request.use((config) => {
    config.headers.Authorization = `Bearer ${tokenManager.getToken()}`;
    return config;
  });

  instance.interceptors.response.use(
    (response: AxiosResponse) => {
      try {
        if (validateMpid) validateMpidHeader(response, tokenManager);
        return response;
      } catch (error) {
        if (error instanceof InvalidMpidHeaderError) {
          broker.publish(BROKER_EVENTS.shell.mpidHeaderInvalid, {
            request: response.config,
          });
        }
        return Promise.reject(error);
      }
    },
    async (error) => {
      const originalRequest = error.config;
      if (error.response?.status === 401 && !originalRequest._retry) {
        originalRequest._retry = true;
        try {
          const newToken = await tokenManager.refreshToken();
          originalRequest.headers.Authorization = `Bearer ${newToken}`;
          return instance(originalRequest);
        } catch (refreshError) {
          console.error("Error refreshing token:", refreshError);
          return Promise.reject(error);
        }
      }
      return Promise.reject(error);
    },
  );

  return instance;
};

let instance;

//TODO: Test concurrent request that get intercepted by expired token and
// An option is to use subscribers to enque unauth requests and only refresh one token and retry the others with the new token
export interface HttpClient {
  request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>;
}
export const createHttpClient = (
  tokenManager: TokenManager,
  broker: PrimariaBroker,
): HttpClient => {
  if (!instance) {
    instance = createAxiosInstance(tokenManager, broker);
  }
  return { request: instance.request };
};
