import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import toast from 'react-hot-toast';
import { appEnv } from '@/shared/config/env';
import { API_ENDPOINTS } from '@/shared/constants/apiEndpoints';
import { ROUTES } from '@/shared/constants/routes';
import { extractApiErrorMessage } from '@/shared/utils/apiErrors';

const PUBLIC_AUTH_PATHS = [
  ROUTES.LOGIN,
  ROUTES.AUTH_MAGIC,
  ROUTES.AUTH_ACCEPT_INVITE,
] as const;

function isPublicAuthPath(pathname: string): boolean {
  return PUBLIC_AUTH_PATHS.some(
    (path) => pathname === path || pathname.startsWith(`${path}/`),
  );
}

function isSessionProbeRequest(url: string | undefined): boolean {
  return typeof url === 'string' && url.includes(API_ENDPOINTS.AUTH.ME);
}

/** 404 is an expected outcome (e.g. ticket has no SLA record yet). */
function isOptionalNotFoundRequest(url: string | undefined): boolean {
  if (typeof url !== 'string') {
    return false;
  }

  if (/\/sla(\/|$|\?)/.test(url)) {
    return true;
  }

  // Project workspace pages render their own not-found UI (stale bookmarks after DB reset).
  if (/\/projects\/[0-9a-f-]{36}(\/command-center)?(\?|$)/i.test(url)) {
    return true;
  }

  return false;
}

type LaravelErrorBody = {
  message?: string;
  errors?: Record<string, string[]>;
};

function isLaravelErrorBody(value: unknown): value is LaravelErrorBody {
  return typeof value === 'object' && value !== null;
}

function toastApiError(data: unknown, fallback: string): void {
  toast.error(extractApiErrorMessage(data, fallback) ?? fallback);
}

// Get CSRF token from meta tag
const getCSRFToken = (): string | null => {
  return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || null;
};

// Create axios instance
export const apiClient = axios.create({
  baseURL: appEnv.apiBaseUrl,
  timeout: appEnv.apiTimeoutMs,
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
    'X-Requested-With': 'XMLHttpRequest',
  },
  withCredentials: true,
});

// Request interceptor
apiClient.interceptors.request.use(
  (config) => {
    // Add CSRF token
    const csrfToken = getCSRFToken();
    if (csrfToken) {
      config.headers['X-CSRF-TOKEN'] = csrfToken;
    }

    const xsrfToken = document.cookie
      .split('; ')
      .find((row) => row.startsWith('XSRF-TOKEN='))
      ?.split('=')[1];

    if (xsrfToken) {
      config.headers['X-XSRF-TOKEN'] = decodeURIComponent(xsrfToken);
    }

    // Add locale
    const locale = localStorage.getItem('locale') || 'en';
    config.headers['Accept-Language'] = locale;

    const departmentMatch = config.url?.match(/\/departments\/([0-9a-f-]{36})/i);
    if (departmentMatch?.[1]) {
      config.headers['X-Department-Id'] = departmentMatch[1];
    }

    const requestId =
      (typeof crypto !== 'undefined' && 'randomUUID' in crypto
        ? crypto.randomUUID()
        : `req-${Date.now()}`);
    config.headers['X-Request-Id'] = requestId;

    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// Response interceptor
apiClient.interceptors.response.use(
  (response: AxiosResponse) => {
    const requestId = response.headers['x-request-id'];
    if (typeof requestId === 'string' && requestId !== '') {
      sessionStorage.setItem('mvhd:last-request-id', requestId);
    }
    return response;
  },
  (error: AxiosError) => {
    // Handle 401 Unauthorized
    if (error.response?.status === 401) {
      const requestUrl = error.config?.url;
      const shouldRedirect =
        !isSessionProbeRequest(requestUrl) && !isPublicAuthPath(window.location.pathname);

      if (shouldRedirect) {
        const redirect = encodeURIComponent(
          window.location.pathname + window.location.search,
        );
        window.location.href = `${ROUTES.LOGIN}?redirect=${redirect}`;
      }
      return Promise.reject(error);
    }

    // Handle 403 Forbidden
    if (error.response?.status === 403) {
      toast.error('You do not have permission to perform this action');
      return Promise.reject(error);
    }

    // Handle 404 Not Found
    if (error.response?.status === 404) {
      if (!isOptionalNotFoundRequest(error.config?.url)) {
        toast.error('Resource not found');
      }
      return Promise.reject(error);
    }

    // Handle 400 Bad Request (business rules)
    if (error.response?.status === 400) {
      toastApiError(error.response.data, 'Request could not be completed.');
      return Promise.reject(error);
    }

    // Handle 422 Validation Error
    if (error.response?.status === 422) {
      const raw = error.response.data;
      if (isLaravelErrorBody(raw)) {
        if (raw.errors) {
          Object.values(raw.errors).forEach((messages) => {
            if (Array.isArray(messages)) {
              messages.forEach((message) => toast.error(String(message)));
            }
          });
        } else if (raw.message) {
          toast.error(raw.message);
        }
      }
      return Promise.reject(error);
    }

    // Handle 429 Too Many Requests
    if (error.response?.status === 429) {
      toast.error('Too many requests. Please slow down.');
      return Promise.reject(error);
    }

    // Handle 500 Server Error
    if (error.response?.status === 500) {
      toast.error('A server error occurred. Please try again later.');
      return Promise.reject(error);
    }

    // Handle timeout
    if (error.code === 'ECONNABORTED') {
      toast.error('The request timed out. Please try again.');
      return Promise.reject(error);
    }

    // Handle network error
    if (error.message === 'Network Error' || !error.response) {
      toast.error('Network error. Please check your connection.');
      return Promise.reject(error);
    }

    // Generic error
    toastApiError(error.response?.data, 'An unexpected error occurred');

    return Promise.reject(error);
  }
);

// Typed API methods
export const api = {
  get: <T = unknown>(url: string, config?: AxiosRequestConfig) =>
    apiClient.get<T>(url, config).then((res) => res.data),

  post: <T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig) =>
    apiClient.post<T>(url, data, config).then((res) => res.data),

  put: <T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig) =>
    apiClient.put<T>(url, data, config).then((res) => res.data),

  patch: <T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig) =>
    apiClient.patch<T>(url, data, config).then((res) => res.data),

  delete: <T = unknown>(url: string, config?: AxiosRequestConfig) =>
    apiClient.delete<T>(url, config).then((res) => res.data),
};

export default api;
