import {
  BaseQueryFn,
  FetchArgs,
  fetchBaseQuery,
  FetchBaseQueryError,
  createApi,
} from '@reduxjs/toolkit/query/react';
import { logout } from '@/redux/slices/authSlice';
import {
  getAccessToken,
  getRefreshToken,
  setAuthTokens,
  clearAuthTokens,
} from '@/lib/auth-cookies';
import { IAPISuccesResponse } from './types/common';
import { API_BASE_URL, API_ENDPOINTS, REDUX_PERSIST_KEY } from './constant';

const baseQuery = fetchBaseQuery({
  baseUrl: API_BASE_URL,
  prepareHeaders: (headers) => {
    const token = getAccessToken();
    if (token) headers.set('Authorization', `Bearer ${token}`);
    return headers;
  },
  paramsSerializer: (params) => {
    const searchParams = new URLSearchParams();
    for (const [key, value] of Object.entries(params)) {
      if (Array.isArray(value)) {
        value.forEach((v) => searchParams.append(key, String(v)));
      } else if (value !== undefined && value !== null) {
        searchParams.set(key, String(value));
      }
    }
    return searchParams.toString();
  },
});

// Once a session is unrecoverable we log out exactly once. Without this guard
// every in-flight query that 401s would call resetApiState(), which re-triggers
// `refetchOnMountOrArgChange` queries that 401 again — an infinite refetch loop
// that spins until the redirect below finally unloads the page.
let isLoggingOut = false;

// Single-flight the token refresh. A page fires many queries at once; on an
// expired token they all 401 simultaneously. Sharing one refresh promise means
// `/refreshToken` is hit once (not once per query), so a rotating refresh token
// isn't consumed by the first request and then rejected for all the others.
let refreshPromise: Promise<boolean> | null = null;

const forceLogoutAndRedirect = (
  api: Parameters<
    BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryError>
  >[1],
) => {
  if (isLoggingOut) return;
  isLoggingOut = true;

  clearAuthTokens();
  api.dispatch(logout());
  api.dispatch(baseApi.util.resetApiState());

  if (typeof window === 'undefined') return;

  try {
    window.localStorage.removeItem(`persist:${REDUX_PERSIST_KEY}`);
  } catch {
    // localStorage may be unavailable (private mode, quota, etc.) — ignore
  }

  if (window.location.pathname !== '/') {
    window.location.href = '/';
  } else {
    window.location.reload();
  }
};

const baseQueryWithReauth: BaseQueryFn<
  string | FetchArgs,
  unknown,
  FetchBaseQueryError
> = async (args, api, extraOptions) => {
  let result = await baseQuery(args, api, extraOptions);

  if (result.error && result.error.status === 401 && !isLoggingOut) {
    const refreshToken = getRefreshToken();

    if (refreshToken) {
      // Reuse an in-flight refresh if one is already running; otherwise start it.
      if (!refreshPromise) {
        refreshPromise = (async () => {
          const refreshResult = await baseQuery(
            {
              url: API_ENDPOINTS.refreshToken,
              method: 'POST',
              body: { refreshToken },
            },
            api,
            extraOptions
          );

          const refreshData = (
            refreshResult.data as
              | IAPISuccesResponse<{ token: string; refreshToken: string }>
              | undefined
          )?.responseData;

          if (refreshData) {
            setAuthTokens(refreshData.token, refreshData.refreshToken);
            return true;
          }
          return false;
        })();
      }

      let refreshed = false;
      try {
        refreshed = await refreshPromise;
      } finally {
        // Clear so a genuinely-later 401 (new expiry) can refresh again.
        refreshPromise = null;
      }

      if (refreshed) {
        result = await baseQuery(args, api, extraOptions);
        if (result.error && result.error.status === 401) {
          forceLogoutAndRedirect(api);
        }
      } else {
        forceLogoutAndRedirect(api);
      }
    } else {
      forceLogoutAndRedirect(api);
    }
  }

  if (result.data) {
    const apiResponse = result.data as IAPISuccesResponse<unknown>;
    if (apiResponse && apiResponse.responseData !== undefined) {
      result.data = apiResponse.responseData;
    }
  }

  return result;
};

export const baseApi = createApi({
  reducerPath: 'api',
  baseQuery: baseQueryWithReauth,
  tagTypes: ['User', 'Auth', 'BlogPost', 'Answer', 'AnswerComment', 'QuestionDetail', 'FollowList', 'Bookmark', 'Notification', 'Plan'],
  endpoints: () => ({}),
});
