import axios, { AxiosInstance, AxiosRequestConfig } from "axios";
import { getAccessTokenCookie } from "@utils/auth";

const axiosInstance: AxiosInstance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
});

export enum APIPath {
  TAGS = "/tags/:id",
  EVENTS = "/events/:id",
  FEED = "/feed/:id",
  JOBADS = "/jobads/:id",
  SIGNUPS = "/signup/:id",
  SIGNUPS_EDIT = "/signup/:id/edit",
  SIGNUP_FORMS = "/signupForm/:id",
  SIGNUP_FORMS_EMAIL = "/signupForm/:id/sendemail",
  SIGNUP_FORMS_SIGNUPS = "/signupForm/:id/signups",
  AUTH_TOKEN_GENERATE = "/token",
  AUTH_TOKEN_VERIFY = "/token/verify",
  AUTH_TOKEN_REFRESH = "/token/refresh",
}

export type API = {
  path: APIPath;
  urlParams?: {
    id?: string | number;
  };
  queryParams?: {
    limit?: number;
    offset?: number;
    since?: Date;
    uuid?: string;
  };
  authenticated?: boolean;
};

type Headers = {
  Authorization?: string;
};

const getAuthHeader = (): string => {
  const jwt = getAccessTokenCookie();
  return `Bearer ${jwt}`;
};

const getHeaders = (auth?: boolean): Headers => {
  if (auth) {
    return {
      Authorization: getAuthHeader(),
    };
  }
  return {};
};

const fillUrlParams = (apiPath: APIPath, params: API["urlParams"] = {}): string => {
  const path = apiPath
    .split("/")
    .map((urlComponent) => {
      // fill in each placeholder component like ':id' with value from params
      if (urlComponent.startsWith(":")) {
        const key = urlComponent.substring(1);
        const value = params[key] ?? "";
        return value;
      }
      return urlComponent;
    })
    .filter(Boolean)
    .join("/");
  // code above strips leading and trailing '/' from path
  return `/${path}/`;
};

const callBackendAPI = async <RequestType, ResponseType>(
  path: APIPath,
  urlParams: API["urlParams"],
  queryParams: API["queryParams"],
  method: AxiosRequestConfig["method"],
  headers: Headers,
  requestBody: RequestType,
): Promise<ResponseType> => {
  const url = fillUrlParams(path, urlParams);
  const request: AxiosRequestConfig = {
    url,
    method,
    headers,
    params: queryParams,
    data: requestBody,
    responseType: "json",
  };
  const response = await axiosInstance.request<ResponseType>(request);

  const arrayResp = (response.data as { results?: ResponseType });
  if (Array.isArray(arrayResp.results)) {
    return arrayResp.results;
  }
  return response.data;
};

export const getBackendAPI = async <ResponseType>({
  path, urlParams, queryParams, authenticated,
}: API): Promise<ResponseType> => {
  const headers = getHeaders(authenticated);
  return callBackendAPI<undefined, ResponseType>(path, urlParams, queryParams, "GET", headers, undefined);
};

export const postBackendAPI = async <RequestType, ResponseType>({
  path, urlParams, queryParams, authenticated,
}: API, body: RequestType): Promise<ResponseType> => {
  const headers = getHeaders(authenticated);
  return callBackendAPI<RequestType, ResponseType>(path, urlParams, queryParams, "POST", headers, body);
};

export const putBackendAPI = async <RequestType, ResponseType>({
  path, urlParams, queryParams, authenticated,
}: API, body: RequestType): Promise<ResponseType> => {
  const headers = getHeaders(authenticated);
  return callBackendAPI<RequestType, ResponseType>(path, urlParams, queryParams, "PUT", headers, body);
};

export const deleteBackendAPI = async <ResponseType>({
  path, urlParams, queryParams, authenticated,
}: API): Promise<ResponseType> => {
  const headers = getHeaders(authenticated);
  return callBackendAPI<undefined, ResponseType>(path, urlParams, queryParams, "DELETE", headers, undefined);
};

export const fetcher = <ResponseType>({
  path, urlParams, queryParams, authenticated,
}: API) => getBackendAPI<ResponseType>({
    path, urlParams, queryParams, authenticated,
  });
