import { deleteAccount, updateAccountTokens } from "@/repository/account";
import { useDbStore } from "@/store/db";
import { useAuth } from "@/store/auth";
import Axios, { type AxiosResponse } from "axios";
import { refreshTokenApi } from "./refresh";

import { isExpired } from "react-jwt";

export const axios = Axios.create({
  headers: {
    "Content-Type": "application/json",
  },
});

let refreshQueue: ((token: string | null) => void)[] = [];

const waitForTokenRefresh = () => {
  return new Promise<string | null>((resolve) => {
    refreshQueue.push((token: string | null) => resolve(token));
  });
};

const processRefreshQueue = async (token: string | null) => {
  refreshQueue.forEach((resolve) => resolve(token));
  refreshQueue = [];
};

const logout = async (accountId: string) => {
  const db = useDbStore.getState().db;
  const requireSignIn = useAuth.getState().requireSignIn;

  if (db) {
    await deleteAccount(db, accountId);
  }
  requireSignIn?.();
};

const refreshToken = async (
  accountId: string,
  refreshToken: string,
): Promise<{ access: string; refresh: string }> => {
  const db = useDbStore.getState().db;
  const loadAccounts = useAuth.getState().loadAccounts;

  if (!db) {
    console.log("No database connection available.");
    throw new Error("No database connection available.");
  }

  try {
    const response = await refreshTokenApi(refreshToken);

    await updateAccountTokens(db, {
      accountId: accountId,
      access: response.access,
      refresh: response.refresh,
    });

    processRefreshQueue(response.access);

    return { access: response.access, refresh: response.refresh };
  } catch (err) {
    console.error("Token refresh failed:", err);
    processRefreshQueue(null);
    throw err;
  } finally {
    localStorage.removeItem("refreshing");
    loadAccounts?.();
    window.guard.refreshing = false;
  }
};

axios.interceptors.request.use(
  async (request) => {
    const account = useAuth.getState().activeAccount;
    let token: string | null = account?.access ?? null;

    if (!token || !isExpired(token)) {
      request.headers["Authorization"] = `Bearer ${token}`;
      return request;
    }

    if (!window.guard.refreshing) {
      console.log(`request to ${request.url} is refreshing token`);
      window.guard.refreshing = true;
      try {
        const { access } = await refreshToken(
          account!.accountId,
          account!.refresh,
        );
        token = access;
      } catch (err) {
        console.error("Token refresh failed:", err);
        await logout(account!.accountId);
        throw err;
      }
    } else {
      console.log(`request to ${request.url} is waiting for token`);
      token = await waitForTokenRefresh();
      console.log(`request to ${request.url} waited for token:`, token);
    }

    if (!token) {
      // logout should be triggered by main process (refreshToken)
      // await logout(account!.accountId);
      throw new Error("No token available");
    }

    request.headers["Authorization"] = `Bearer ${token}`;
    return request;
  },
  (error) => Promise.reject(error),
);

export const handleApiError = async (response: AxiosResponse) => {
  const text =
    response.data?.error ||
    response.data?.toString?.() ||
    "unexpected error happened";
  return new Error(text[0].toUpperCase() + text.slice(1));
};
