import { redirect } from "react-router";
import Cookies from "js-cookie";
import type { CoreResponse } from "./types";

let refreshPromise: Promise<boolean> | null = null;

export function createCoreAPIClient() {
  const getEnv = (key: string) => {
    if (
      typeof import.meta !== "undefined" &&
      import.meta.env &&
      import.meta.env[key]
    ) {
      return import.meta.env[key];
    }
    if (typeof process !== "undefined" && process.env && process.env[key]) {
      return process.env[key];
    }
    return undefined;
  };

  const isBrowser = typeof window !== "undefined";
  const baseUrl = isBrowser
    ? "/api/proxy"
    : ((getEnv("VITE_CORE_API") || getEnv("CORE_API_URL") || "") as string);
  const apiKey = (getEnv("VITE_CORE_API_KEY") ||
    getEnv("CORE_API_KEY") ||
    "") as string;

  async function refreshAuth(headers?: HeadersInit): Promise<boolean> {
    const reqHeaders = new Headers({
      "x-api-key": apiKey,
      Accept: "application/json",
      "Content-Type": "application/json",
    });
    if (headers) {
      new Headers(headers).forEach((value, key) => reqHeaders.set(key, value));
    }

    try {
      const refreshResponse = await fetch(
        `${baseUrl}/v2/admin/console-user/session-rotate`,
        {
          method: "PATCH",
          headers: reqHeaders,
          credentials: "include",
        },
      );
      return refreshResponse.ok;
    } catch {
      return false;
    }
  }

  return async function coreAPIFetcher<TData>(
    path: string,
    init?: RequestInit,
    _needRawRes?: boolean,
  ): Promise<CoreResponse<TData>> {
    const doFetch = async () => {
      const headersObject: Record<string, string> = {
        "x-api-key": apiKey,
        Accept: "application/json",
        "x-log-category": "UI",
      };

      /*
       * JSON content type for JSON bodies only.
       *
       * FormData must set its own `multipart/form-data; boundary=…`, and the
       * boundary is generated by fetch — overriding the header here left the
       * server with a body it could not parse, which is why avatar uploads
       * failed. Same for Blob and URLSearchParams, which carry their own types.
       */
      const body = init?.body;
      const carriesOwnContentType =
        typeof FormData !== "undefined" && body instanceof FormData
          ? true
          : typeof Blob !== "undefined" && body instanceof Blob
            ? true
            : typeof URLSearchParams !== "undefined" &&
              body instanceof URLSearchParams;

      if (
        init?.method &&
        !["GET", "HEAD"].includes(init.method.toUpperCase()) &&
        !carriesOwnContentType
      ) {
        headersObject["Content-Type"] = "application/json";
      }

      const reqHeaders = new Headers(headersObject);

      if (init?.headers) {
        new Headers(init.headers).forEach((value, key) =>
          reqHeaders.set(key, value),
        );
      }

      return fetch(`${baseUrl}${path}`, {
        ...init,
        headers: reqHeaders,
        cache: "no-store",
        credentials: "include",
      });
    };
    let response = await doFetch();

    if (response.status === 401) {
      if (!refreshPromise) {
        // Forward headers to refreshAuth in case we are on the server
        refreshPromise = refreshAuth(init?.headers).finally(() => {
          refreshPromise = null;
        });
      }

      const success = await refreshPromise;

      if (success) {
        response = await doFetch();
        if (response.status === 401) {
          // Retry failed, logout
          if (typeof window !== "undefined") {
            Cookies.remove("isLoggedIn");
            window.location.href = "/auth/logout";
          }
          throw redirect("/auth/logout");
        }
      } else {
        // Refresh failed, logout
        if (typeof window !== "undefined") {
          Cookies.remove("isLoggedIn");
          window.location.href = "/auth/logout";
        }
        throw redirect("/auth/logout");
      }
    }

    if (response.status === 403) {
      if (typeof window !== "undefined") {
        Cookies.remove("isLoggedIn");
        window.location.href = "/auth/logout";
      }
      throw redirect("/auth/logout");
    }

    if (!response.ok) {
      try {
        return (await response.json()) as CoreResponse<TData>;
      } catch {
        // Body is not JSON (e.g. Hono plain-text 500) — return a safe error object
        return {
          success: false,
          message: `Core API error: ${response.status} ${response.statusText}`,
          data: null as unknown as TData,
        } as CoreResponse<TData>;
      }
    }

    if (_needRawRes) {
      return {
        data: response as TData,
        message: "",
        success: true,
      };
    }

    return (await response.json()) as CoreResponse<TData>;
  };
}

export function createRawCoreAPIClient() {
  const getEnv = (key: string) => {
    if (
      typeof import.meta !== "undefined" &&
      import.meta.env &&
      import.meta.env[key]
    ) {
      return import.meta.env[key];
    }
    if (typeof process !== "undefined" && process.env && process.env[key]) {
      return process.env[key];
    }
    return undefined;
  };

  const isBrowser = typeof window !== "undefined";
  const baseUrl = isBrowser
    ? "/api/proxy"
    : ((getEnv("VITE_CORE_API") || getEnv("CORE_API_URL") || "") as string);
  const apiKey = (getEnv("VITE_CORE_API_KEY") ||
    getEnv("CORE_API_KEY") ||
    "") as string;

  return async function coreAPIFetcher(
    path: string,
    init?: RequestInit,
  ): Promise<Response> {
    const reqHeaders = new Headers({
      "x-api-key": apiKey,
      Accept: "application/json",
      "Content-Type": "application/json",
    });
    if (init?.headers) {
      new Headers(init.headers).forEach((value, key) =>
        reqHeaders.set(key, value),
      );
    }

    return fetch(`${baseUrl}${path}`, {
      ...init,
      headers: reqHeaders,
      cache: "no-store",
      credentials: "include",
    });
  };
}
