import { UseQueryOptions, QueryObserverOptions } from "@tanstack/react-query";
import axios, { AxiosError, InternalAxiosRequestConfig } from "axios";
import { URL_CONSTANTS } from "@/constants/URLS";
import { API_BASE_URL } from "@/constants/apiConfig";
import { captureApiError } from "@/lib/posthog";

const client = axios.create({
  baseURL: API_BASE_URL,
});

function getCookie(name: string): string | undefined {
  return document.cookie
    .split("; ")
    .find((row) => row.startsWith(`${name}=`))
    ?.split("=")[1];
}

function setCookie(name: string, value: string, days: number) {
  const expires = new Date();
  expires.setDate(expires.getDate() + days);
  document.cookie = `${name}=${value}; path=/; expires=${expires.toUTCString()}; SameSite=Lax`;
}

function clearAuthCookies() {
  [
    "auth-token",
    "refresh-token",
    "auth-user",
    "current-position-id",
    "selected-position-id",
  ].forEach((name) => {
    document.cookie = `${name}=; Max-Age=0; path=/`;
  });
}

// Attach auth token to every request
client.interceptors.request.use((config) => {
  const token = getCookie("auth-token");
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// Token refresh state
let refreshPromise: Promise<string> | null = null;

/**
 * Single-flight token refresh: concurrent callers (the 401 interceptor and
 * the proactive scheduler) share one in-flight request so the refresh token
 * is only rotated once. Throws if no refresh token is stored or the server
 * rejects it — callers decide how to end the session.
 */
async function refreshSessionTokens(): Promise<string> {
  refreshPromise ??= (async () => {
    const refreshToken = getCookie("refresh-token");
    if (!refreshToken) {
      throw new Error("missing refresh token");
    }

    type TokenPair = { token: string; refreshToken: string };
    const { data } = await client.post<
      Partial<TokenPair> & { data?: TokenPair }
    >(URL_CONSTANTS.AUTH.REFRESH_TOKEN, { refreshToken });
    // The API returns the pair flat ({ success, token, refreshToken }); accept
    // a { data: { ... } }-wrapped shape too so a transform change can't
    // silently break refresh again.
    const payload = data.data ?? data;
    if (!payload.token || !payload.refreshToken) {
      throw new Error("malformed refresh-token response");
    }
    setCookie("auth-token", payload.token, 7);
    setCookie("refresh-token", payload.refreshToken, 7);
    return payload.token;
  })().finally(() => {
    refreshPromise = null;
  });

  return refreshPromise;
}

// Handle auth errors globally with token refresh
client.interceptors.response.use(
  (response) => response,
  async (error: AxiosError) => {
    const originalRequest = error.config as InternalAxiosRequestConfig & {
      _retry?: boolean;
    };

    // Report the failure to PostHog. Hooked here rather than inside
    // `emitApiError`, which stays silent on suppressed paths (warehouse /
    // mile / onboarding) — those failures still need reporting.
    // 401s are skipped: an expired session is refreshed below, not a defect.
    if (!error.response || error.response.status !== 401) {
      captureApiError(error);
    }

    // Don't intercept if:
    // - no response (network error)
    // - status is not 401
    // - already retried
    // - it's the refresh endpoint itself
    // - it's a credential endpoint (401 there = wrong credentials, not an
    //   expired session — refreshing would mask the real error)
    if (
      !error.response ||
      error.response.status !== 401 ||
      originalRequest._retry ||
      originalRequest.url === URL_CONSTANTS.AUTH.REFRESH_TOKEN ||
      originalRequest.url === URL_CONSTANTS.AUTH.LOGIN ||
      originalRequest.url === URL_CONSTANTS.USERS.SIGN_UP
    ) {
      // Surface the server's actual error message in the global error modal
      // (401s are handled by the session flow below/redirects, so skip them).
      if (error.response && error.response.status !== 401) {
        // const payload = extractApiErrorPayload(error);
        // if (payload) emitApiError(payload);
      }
      return Promise.reject(error);
    }

    // Nothing to refresh with (e.g. not logged in yet) — surface the
    // original error instead of a confusing refresh failure.
    if (!getCookie("refresh-token")) {
      if (getCookie("auth-token")) {
        // Half-broken cookie state; reset it.
        clearAuthCookies();
      }
      return Promise.reject(error);
    }

    originalRequest._retry = true;

    try {
      const token = await refreshSessionTokens();
      originalRequest.headers.Authorization = `Bearer ${token}`;
      return client(originalRequest);
    } catch (refreshError) {
      clearAuthCookies();
      return Promise.reject(refreshError);
    }
  },
);

export { client, clearAuthCookies, getCookie, refreshSessionTokens };
export type { UseQueryOptions, QueryObserverOptions };
