import axios from "axios";
import { useAuth } from "../composables/useAuth.js";

/** @type {import("vue-router").Router | null} */
let apiRouter = null;

/** Call once from the app (e.g. after creating the router) so API error handling can navigate. */
export function setApiRouter(router) {
  apiRouter = router;
}

function getRouter() {
  if (!apiRouter) {
    throw new Error(
      "[@maestro/core] setApiRouter(router) must be called before API responses that trigger navigation.",
    );
  }
  return apiRouter;
}

// API base URL: use VITE_API_BASE_URL from .env, or relative /api (same host as the page)
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "/api";
// Origin for CSRF cookie: must hit the backend that sets the session (same origin when proxy, or API origin when cross-origin)
const API_ORIGIN = API_BASE_URL.startsWith("http")
  ? new URL(API_BASE_URL).origin
  : typeof window !== "undefined"
    ? window.location.origin
    : "";

// Create axios instance: session cookie (BFF), no Bearer token; withCredentials required for cookies
const apiClient = axios.create({
  baseURL: API_BASE_URL,
  withCredentials: true,
  withXSRFToken: true,
  headers: {
    "Content-Type": "application/json",
    Accept: "application/json",
    "X-Requested-With": "XMLHttpRequest",
  },
});

/**
 * Fetch Sanctum CSRF cookie. Call once before any state-changing request (or on app boot for protected area).
 * Uses API_ORIGIN so the cookie is set by the same host that will validate it.
 */
let csrfReady = false;
export async function ensureCsrf() {
  if (csrfReady) return;
  const base =
    API_ORIGIN || (typeof window !== "undefined" ? window.location.origin : "");
  await axios.get(`${base}/sanctum/csrf-cookie`, { withCredentials: true });
  csrfReady = true;
}
export function resetCsrf() {
  csrfReady = false;
}

// Response interceptor: 401 with silent refresh / force_reauth, 403, 404, 5xx
apiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    // Ignore canceled requests (AbortController / axios cancel).
    // These are expected when navigating away or starting a newer request.
    if (error?.code === "ERR_CANCELED" || error?.name === "CanceledError") {
      return Promise.reject(error);
    }

    if (!error.response) {
      const isAuthenticated = false;
      const errorMessage = encodeURIComponent(
        error.message || "Unable to connect. Please try again.",
      );
      getRouter().push({ name: "oops", query: { message: errorMessage } });
      return Promise.reject({ message: error.message, errors: {} });
    }

    const { data, status } = error.response;

    // 419 CSRF token mismatch: refresh cookie and retry once
    if (status === 419) {
      if (!error.config._csrfRetried) {
        error.config._csrfRetried = true;
        resetCsrf();
        try {
          await ensureCsrf();
          return apiClient.request(error.config);
        } catch (_) {
          // fall through to reject
        }
      }
      return Promise.reject({
        message: data?.message || "Session expired. Please refresh the page.",
        errors: data?.errors || {},
        status,
      });
    }

    // 401: try silent refresh unless force_reauth
    if (status === 401) {
      // Requests flagged with _skipAuthRetry (e.g. fetchUser on page load) should
      // just fail silently — no refresh attempt, no redirect.
      if (error.config._skipAuthRetry) {
        return Promise.reject({
          message: data?.message || "Unauthorized",
          errors: data?.errors || {},
          status,
        });
      }

      if (data?.force_reauth) {
        window.location.href = `${API_BASE_URL.replace(/\/$/, "")}/auth/microsoft/redirect`;
        return new Promise(() => {});
      }
      if (!error.config._retried) {
        error.config._retried = true;
        try {
          await axios.get(
            `${API_BASE_URL.replace(/\/$/, "")}/auth/refresh-session`,
            { withCredentials: true },
          );
          return apiClient.request(error.config);
        } catch (_) {
          if (window.location.pathname !== "/login") {
            window.location.href = "/login";
          }
          return new Promise(() => {});
        }
      }
      if (window.location.pathname !== "/login") {
        window.location.href = "/login";
      }
      return Promise.reject({
        message: data?.message || "Unauthorized",
        errors: data?.errors || {},
        status,
      });
    }

    // 403 Forbidden: use backend auth state (useAuth) instead of cookie check
    // Store message in sessionStorage so URL stays clean; AccessDenied reads and clears it
    if (status === 403) {
      const { isAuthenticated } = useAuth();
      if (data?.message && typeof sessionStorage !== "undefined") {
        sessionStorage.setItem("accessDeniedMessage", data.message);
      }
      if (isAuthenticated.value) {
        getRouter().push({ name: "access-denied" });
      } else {
        getRouter().push({ name: "access" });
      }
      return Promise.reject({
        message: data?.message || "Forbidden",
        errors: data?.errors || {},
        status,
      });
    }

    // 404 Not Found → canonical /not-found
    if (status === 404) {
      const errorMessage = encodeURIComponent(
        data?.message || "The requested resource was not found.",
      );
      getRouter().push({ name: "not-found", query: { message: errorMessage } });
      return Promise.reject({
        message: data?.message || "Not Found",
        errors: data?.errors || {},
        status,
      });
    }

    // 423 Locked (step-up auth)
    if (status === 423 && data?.requires_step_up) {
      // TODO: show re-verification dialog, then retry
      return Promise.reject({
        message: data?.message || "Please confirm your identity.",
        errors: data?.errors || {},
        status,
      });
    }

    // 500, 502, 503
    if ([500, 502, 503].includes(status)) {
      let errorMessage = "A server error occurred. Please try again later.";
      if (data?.message) errorMessage = data.message;
      else if (status === 500)
        errorMessage = "Internal server error. Please try again later.";
      else if (status === 502)
        errorMessage = "Bad gateway. The server is temporarily unavailable.";
      else if (status === 503)
        errorMessage = "Service unavailable. Please try again later.";
      const encodedMessage = encodeURIComponent(errorMessage);
      getRouter().push({ name: "oops", query: { message: encodedMessage } });
      return Promise.reject({
        message: errorMessage,
        errors: data?.errors || {},
        status,
      });
    }

    // Default: forward full backend payload so callers can use any extra fields
    // (e.g. domain-specific `reasons` array) while still normalising `message`,
    // `errors` and `status`.
    return Promise.reject({
      ...(data || {}),
      message: data?.message || "An error occurred",
      errors: data?.errors || {},
      status,
    });
  },
);

export default apiClient;
