import { logger } from '@sentry/react';
import axios, {
  type AxiosError,
  type AxiosResponse,
  type InternalAxiosRequestConfig,
} from 'axios';

import { type ResponseError } from 'src/types/services';
import { kickOutUserThrottled, unescapeStrings } from 'src/utils/api';

import { requestManager } from './RequestManager';
import { tokenManager } from './tokens';

const paramsForApiCreation = {
  baseURL: import.meta.env.VITE_BASE_URL,
  headers: {
    'x-humand-origin': 'web',
  },
};

// do not create new axios api instances unless it's a completely separate backend.
const api = axios.create(paramsForApiCreation);

const refreshTokenInterceptor = async (request: InternalAxiosRequestConfig) => {
  // addRequest runs unconditionally so that pre-auth requests (login, refresh, SSO, etc.)
  // are still tracked and aborted by requestManager.abortAllRequests() on logout.
  // The response interceptors handle removeRequest for all paths, including skipInterceptor ones.
  requestManager.addRequest(request);

  // Skip only when caller opted out (e.g. refresh, token-login, SSO, login). Otherwise always run
  // token logic so we refresh if needed and set a valid token (even if request had an expired one).
  if (request.skipInterceptor) {
    return request;
  }

  let token: string | null;
  try {
    token = await tokenManager.getValidAccessToken();
  } catch (error) {
    // Unexpected throw during token evaluation (e.g. malformed JWT) — clear auth state
    // immediately so the app does not loop retrying requests with a broken token.
    logger.error(
      'Token evaluation threw unexpectedly — signing out and redirecting to login',
      {
        message:
          'Token evaluation threw unexpectedly — signing out and redirecting to login',
        module: 'Web',
        component: 'API',
        error: error instanceof Error ? error.message : String(error),
      },
    );
    kickOutUserThrottled(true);
    return Promise.reject(error);
  }

  if (token) {
    if (request.headers) {
      request.headers.Authorization = `Bearer ${token}`;
    }
  } else {
    // null here means either (a) no tokens at all (public / login routes — fine) or
    // (b) tokens existed but the refresh loop failed definitively. In case (b) the
    // in-memory tokens are still present (removeTokens is never called by the refresh
    // loop), so we can distinguish the two and kick the user out right away instead of
    // waiting for a server 401 that may not carry the expected error code.
    const { accessToken, refreshToken } = tokenManager.getCorrectToken();
    if (accessToken || refreshToken) {
      logger.error(
        'Refresh loop failed definitively — tokens still in memory but no valid token returned; signing out',
        {
          message:
            'Refresh loop failed definitively — tokens still in memory but no valid token returned; signing out',
          module: 'Web',
          component: 'API',
        },
      );
      kickOutUserThrottled(true);
      return Promise.reject(new Error('Session expired'));
    }
  }

  return request;
};

api.interceptors.request.use(refreshTokenInterceptor);

const unescapeInterceptor = (response: AxiosResponse) => {
  const shouldUnescape = response.headers['x-content-escaped'];

  if (shouldUnescape) {
    response.data = unescapeStrings(response.data);
  }

  return response;
};

api.interceptors.response.use(unescapeInterceptor);

const removeRequestInterceptor = (response: AxiosResponse) => {
  requestManager.removeRequest(response.config);
  return response;
};

type HumandRequestConfig = InternalAxiosRequestConfig & {
  _isAuthRetry?: boolean;
};

const kickOutUserInterceptor = async (error: AxiosError<ResponseError>) => {
  const { response, config } = error;

  if (config) {
    const typedConfig = config as HumandRequestConfig;

    // Pre-auth endpoints (login, SSO, OTP, refresh) manage their own error responses.
    // The request interceptor already skipped token management for them; the response
    // interceptor should too, so any error (wrong credentials, expired refresh token, etc.)
    // is passed directly to the caller without triggering the retry/logout flow.
    if (typedConfig.skipInterceptor) {
      requestManager.removeRequest(config);
      return Promise.reject(error);
    }

    const url = config.url ?? config.baseURL ?? '';

    if (response?.status === 401) {
      logger.error('Session invalid: API rejected token (401)', {
        message: 'Session invalid: API rejected token (401)',
        module: 'Web',
        component: 'API',
        url,
        status: response?.status,
        code: response?.data?.code,
        error: error instanceof Error ? error.message : String(error),
        isRetry: typedConfig._isAuthRetry ?? false,
      });

      // Already retried once — kick out directly without another attempt
      if (typedConfig._isAuthRetry) {
        requestManager.removeRequest(config);
        logger.error(
          'Session invalid after retry — signing out and redirecting to login',
          {
            message:
              'Session invalid after retry — signing out and redirecting to login',
            module: 'Web',
            component: 'API',
            url,
          },
        );
        kickOutUserThrottled(true);
        return Promise.reject(error);
      }

      // First 401: attempt token refresh and retry before signing out.
      // Note: the request interceptor already called getValidAccessToken() before this
      // request went out. Calling it again here forces a new refresh cycle in case the
      // server rejected a token that was locally valid (e.g. revocation, clock skew).
      // TokenManager deduplication prevents concurrent loops, but a new loop starts if
      // the previous one already completed.
      requestManager.removeRequest(config);
      let freshToken: string | null;
      try {
        freshToken = await tokenManager.getValidAccessToken();
      } catch (refreshError) {
        // Unexpected throw during token refresh — kick out immediately so the user
        // is not left in a broken state with no redirect.
        logger.error(
          'Session invalid: token refresh threw unexpectedly — signing out and redirecting to login',
          {
            message:
              'Session invalid: token refresh threw unexpectedly — signing out and redirecting to login',
            module: 'Web',
            component: 'API',
            url,
            error:
              refreshError instanceof Error
                ? refreshError.message
                : String(refreshError),
          },
        );
        kickOutUserThrottled(true);
        return Promise.reject(refreshError);
      }

      if (!freshToken) {
        logger.error(
          'Session invalid: token refresh failed — signing out and redirecting to login',
          {
            message:
              'Session invalid: token refresh failed — signing out and redirecting to login',
            module: 'Web',
            component: 'API',
            url,
          },
        );
        kickOutUserThrottled(true);
        return Promise.reject(error);
      }

      // Token refreshed — retry the original request.
      // Note: the request interceptor will call getValidAccessToken() again on retry and
      // set the Authorization header, so we do not need to set it here.
      typedConfig._isAuthRetry = true;
      return api(typedConfig);
    }

    requestManager.removeRequest(config);
  }
  return Promise.reject(error);
};

api.interceptors.response.use(removeRequestInterceptor, kickOutUserInterceptor);

export const scormApi = axios.create({
  baseURL: 'https://cloud.scorm.com/api/v2',
  headers: {
    Authorization:
      'Basic NjU3TjRJUllDVDpnVmFSWTJEUExDZ215SUIzeVA2a2tDUmpZcjE4aFJzOUVxMkx2cVdS',
  },
});

export default api;

export const filesApi = axios.create({
  headers: {
    'x-humand-origin': 'web',
  },
});
