import { redirect } from "@remix-run/node";
import {
  isAccessTokenExpired,
  isRefreshTokenExpired,
  sessionStorage,
} from "~/sessions.server";
import { ApiApi, Configuration } from "../../src/api/types";
import { getInternalApiUrl } from "~/utils/api-config";

export interface User {
  access_token: string;
  refresh_token: string;
  access_token_timestamp: number;
  refresh_token_timestamp: number;
}
/**
 * Token manager for handling access and refresh tokens
 * - access_token expires after 1 week
 * - refresh_token expires after 1 month
 */
const protectedRoutes = [
  "/profile/bookmarks",
  "/profile/following",
  "/profile/threads",
  "/profile/wallet",
  "/profile/location",
  "/profile/location/add",
  "/profile/location/edit",
  "/profile/orders",
  "/profile/setting",
  "/cart",
  "/store",
  "/admin",
];
export async function validateTokens(request: Request) {
  const session = await sessionStorage.getSession(
    request.headers.get("cookie"),
  );
  const user: User | null = session.get("user");
  const isProtectedRoute = protectedRoutes.some((route) =>
    request.url.includes(route),
  );
  // If no user data found, redirect to login
  if (!user || !user.access_token) {
    if (isProtectedRoute) {
      throw redirect("/login", {
        headers: {
          "Set-Cookie": await sessionStorage.destroySession(session),
        },
      });
    } else return { user: null };
  }
  // Check if refresh_token is expired
  if (
    user &&
    user.refresh_token_timestamp &&
    isRefreshTokenExpired(user.refresh_token_timestamp)
  ) {
    // If refresh token is expired, clear session and redirect to login
    if (isProtectedRoute) {
      throw redirect("/login", {
        headers: {
          "Set-Cookie": await sessionStorage.destroySession(session),
        },
      });
    } else return { user: null };
  }

  // Check if access_token is expired but refresh_token is still valid
  if (
    user &&
    user.access_token_timestamp &&
    isAccessTokenExpired(user.access_token_timestamp)
  ) {
    try {
      // Try to refresh the access token using the refresh token
      const newTokens = await refreshAccessToken(user.refresh_token);
      // Update session with new tokens and new timestamps
      const currentTime = Date.now();
      session.set("user", {
        ...user,
        access_token: newTokens.access,
        refresh_token: newTokens.refresh,
        access_token_timestamp: currentTime,
        refresh_token_timestamp: currentTime,
      });

      // Return the updated session and new tokens
      return {
        user: session.get("user"),
        headers: {
          "Set-Cookie": await sessionStorage.commitSession(session, {
            expires: new Date(new Date().setMonth(new Date().getMonth() + 1)),
          }),
        },
      };
    } catch (error) {
      // If refresh fails, redirect to login
      console.error("Token refresh failed:", error);
      if (isProtectedRoute) {
        throw redirect("/login", {
          headers: {
            "Set-Cookie": await sessionStorage.destroySession(session),
          },
        });
      } else return { user: null };
    }
  }
  if (request.url.includes("/login")) throw redirect("/");

  // If tokens are valid, return user data
  return { user };
}

/**
 * Function to refresh the access token using the refresh token
 * This calls the API endpoint that handles token refresh
 */
async function refreshAccessToken(refreshToken: string) {
  try {
    const basePath = getInternalApiUrl();
    const config = new Configuration({
      basePath,
      headers: {
        "Content-Type": "application/json",
        accept: "application/json",
      },
    });

    const apiClient = new ApiApi(config);

    const response = await apiClient.apiUserV1TokenRefreshCreate({
      data: {
        refresh: refreshToken,
      },
    });

    // Validate the response data
    if (!response.access || !response.refresh) {
      throw new Error("Invalid refresh token response");
    }

    return response;
  } catch (error) {
    console.error("Error refreshing token:", error);
    throw error;
  }
}
