import axios, {
  type AxiosError,
  type InternalAxiosRequestConfig,
} from "axios";
import type { ApiResponse, AuthTokenResponse } from "./types";
import { useAdminAuthStore } from "@/lib/stores/admin-auth.store";

const baseURL =
  process.env.NEXT_PUBLIC_ADMIN_API_URL ?? "http://localhost:5081";

export const adminApi = axios.create({
  baseURL,
  headers: { "Content-Type": "application/json" },
});

adminApi.interceptors.request.use((config) => {
  if (typeof window !== "undefined") {
    const token = localStorage.getItem("meezi_admin_access_token");
    if (token) config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

/** Result of a refresh attempt. `sessionInvalid` is true ONLY when the session
 *  is definitively gone (no/expired/revoked refresh token) — never for a
 *  transient failure like the admin API being briefly down during a deploy. */
type RefreshResult = { token: string | null; sessionInvalid: boolean };

/**
 * Shared in-flight refresh so a burst of concurrent 401s (the admin dashboard
 * loads cafes, tickets, integrations, settings… at once) triggers exactly one
 * POST /api/admin/auth/refresh — otherwise they race and all but the first send
 * a now-rotated refresh token and get logged out.
 */
let refreshPromise: Promise<RefreshResult> | null = null;

async function refreshAdminToken(): Promise<RefreshResult> {
  if (typeof window === "undefined") return { token: null, sessionInvalid: false };
  const refreshToken = localStorage.getItem("meezi_admin_refresh_token");
  if (!refreshToken) return { token: null, sessionInvalid: true };
  try {
    // Bare axios (not `adminApi`) to avoid recursing through this interceptor.
    const { data } = await axios.post<ApiResponse<AuthTokenResponse>>(
      `${baseURL}/api/admin/auth/refresh`,
      { refreshToken },
      { headers: { "Content-Type": "application/json" } }
    );
    if (data.success && data.data) {
      useAdminAuthStore.getState().setAuth(data.data);
      return { token: data.data.accessToken, sessionInvalid: false };
    }
    return { token: null, sessionInvalid: true };
  } catch (err) {
    // Only a definitive 4xx from /auth/refresh means the session is gone. A
    // network error, timeout, or 5xx (API restarting mid-deploy) is transient —
    // do NOT log the admin out for it.
    const status = (err as AxiosError)?.response?.status;
    const sessionInvalid = status === 400 || status === 401 || status === 403;
    return { token: null, sessionInvalid };
  }
}

function forceAdminLogout(): void {
  if (typeof window === "undefined") return;
  localStorage.removeItem("meezi_admin_access_token");
  localStorage.removeItem("meezi_admin_refresh_token");
  localStorage.removeItem("meezi_admin_auth");
  const locale = window.location.pathname.split("/")[1] ?? "fa";
  window.location.href = `/${locale}/admin/login`;
}

adminApi.interceptors.response.use(
  (response) => response,
  async (error: AxiosError<ApiResponse<unknown>>) => {
    const status = error.response?.status;
    const apiError = error.response?.data?.error;
    const original = error.config as
      | (InternalAxiosRequestConfig & { _retry?: boolean })
      | undefined;
    const onAuthPath = original?.url?.includes("/api/admin/auth/") ?? false;

    // Expired access token → silently refresh once, then replay the request.
    // The 7-day admin access token used to just expire with no renewal, so
    // every page stopped loading data until a manual re-login.
    if (
      status === 401 &&
      original &&
      !original._retry &&
      typeof window !== "undefined" &&
      !onAuthPath
    ) {
      original._retry = true;
      refreshPromise ??= refreshAdminToken().finally(() => {
        refreshPromise = null;
      });
      const { token, sessionInvalid } = await refreshPromise;
      if (token) {
        original.headers.Authorization = `Bearer ${token}`;
        return adminApi(original);
      }
      // Couldn't refresh. Log out ONLY if the session is definitively invalid;
      // a transient failure (API redeploying) just rejects so it can retry.
      if (sessionInvalid) forceAdminLogout();
      return Promise.reject(
        apiError?.code
          ? new AdminApiClientError(apiError.code, apiError.message)
          : error
      );
    }

    if (apiError?.code) {
      return Promise.reject(new AdminApiClientError(apiError.code, apiError.message));
    }
    return Promise.reject(error);
  }
);

export class AdminApiClientError extends Error {
  constructor(
    public readonly code: string,
    message: string
  ) {
    super(message);
    this.name = "AdminApiClientError";
  }
}

export async function adminGet<T>(url: string): Promise<T> {
  const { data } = await adminApi.get<ApiResponse<T>>(url);
  if (!data.success || data.data === undefined) {
    throw new Error(data.error?.message ?? "Request failed");
  }
  return data.data;
}

export async function adminPost<T>(url: string, body?: unknown): Promise<T> {
  const { data } = await adminApi.post<ApiResponse<T>>(url, body);
  if (!data.success || data.data === undefined) {
    throw new Error(data.error?.message ?? "Request failed");
  }
  return data.data;
}

export async function adminPut<T>(url: string, body: unknown): Promise<T> {
  const { data } = await adminApi.put<ApiResponse<T>>(url, body);
  if (!data.success || data.data === undefined) {
    throw new Error(data.error?.message ?? "Request failed");
  }
  return data.data;
}

export async function adminPatch<T>(url: string, body: unknown): Promise<T> {
  const { data } = await adminApi.patch<ApiResponse<T>>(url, body);
  if (!data.success || data.data === undefined) {
    throw new Error(data.error?.message ?? "Request failed");
  }
  return data.data;
}

export async function adminDelete(url: string): Promise<void> {
  const { data } = await adminApi.delete<ApiResponse<unknown>>(url);
  if (!data.success) {
    throw new Error(data.error?.message ?? "Request failed");
  }
}
