/**
 * Token Auto-Refresh Middleware
 *
 * Automatically refreshes access token when receiving 401 responses.
 * Implements request queuing to prevent multiple simultaneous refresh attempts.
 */

import { api as apiAccounts } from '../../';
import { authLogger } from '../utils/logger';

// Refresh state management
let isRefreshing = false;
let refreshSubscribers: Array<(token: string | null) => void> = [];

/**
 * Subscribe to token refresh completion
 */
function subscribeTokenRefresh(callback: (token: string | null) => void): void {
  refreshSubscribers.push(callback);
}

/**
 * Notify all subscribers when token is refreshed
 */
function onTokenRefreshed(token: string | null): void {
  refreshSubscribers.forEach(callback => callback(token));
  refreshSubscribers = [];
}

/**
 * Attempt to refresh the access token using the refresh token
 * @returns New access token or null if refresh failed
 */
export async function refreshAccessToken(): Promise<string | null> {
  // If already refreshing, wait for completion
  if (isRefreshing) {
    return new Promise(resolve => {
      subscribeTokenRefresh(token => resolve(token));
    });
  }

  isRefreshing = true;
  authLogger.info('Starting token refresh...');

  try {
    const refreshToken = apiAccounts.getRefreshToken();
    if (!refreshToken) {
      authLogger.warn('No refresh token available for refresh');
      onTokenRefreshed(null);
      return null;
    }

    // Use generated API client with correct URL (/cfg/accounts/token/refresh/)
    const result = await apiAccounts.auth.accountsTokenRefreshCreate({
      refresh: refreshToken,
    });

    const newAccessToken = result.access;

    if (!newAccessToken) {
      authLogger.error('Token refresh response missing access token');
      onTokenRefreshed(null);
      return null;
    }

    // Update tokens in storage
    apiAccounts.setToken(newAccessToken, refreshToken);
    authLogger.info('Token refreshed successfully');

    onTokenRefreshed(newAccessToken);
    return newAccessToken;
  } catch (error) {
    authLogger.error('Token refresh error:', error);
    onTokenRefreshed(null);
    return null;
  } finally {
    isRefreshing = false;
  }
}

/**
 * Check if a response indicates an authentication error
 */
export function isAuthenticationError(response: Response): boolean {
  return response.status === 401;
}

/**
 * Create a fetch wrapper that automatically refreshes tokens on 401
 *
 * @param originalFetch - The original fetch function to wrap
 * @returns Wrapped fetch function with auto-refresh capability
 */
export function createAutoRefreshFetch(originalFetch: typeof fetch): typeof fetch {
  return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
    // Make the initial request
    let response = await originalFetch(input, init);

    // If 401 and we have a refresh token, try to refresh
    if (isAuthenticationError(response) && apiAccounts.getRefreshToken()) {
      authLogger.info('Received 401, attempting token refresh...');

      const newToken = await refreshAccessToken();

      if (newToken) {
        // Retry the request with new token
        const newInit: RequestInit = {
          ...init,
          headers: {
            ...init?.headers,
            Authorization: `Bearer ${newToken}`,
          },
        };

        authLogger.info('Retrying request with new token...');
        response = await originalFetch(input, newInit);
      } else {
        authLogger.warn('Token refresh failed, returning original 401 response');
      }
    }

    return response;
  };
}

/**
 * Check if token is about to expire (within threshold)
 * @param thresholdMs - Time in ms before expiry to consider "expiring soon"
 * @returns true if token expires within threshold
 */
export function isTokenExpiringSoon(thresholdMs: number = 5 * 60 * 1000): boolean {
  const token = apiAccounts.getToken();
  if (!token) return false;

  try {
    // Decode JWT payload (base64)
    const payload = JSON.parse(atob(token.split('.')[1]));
    const expiresAt = payload.exp * 1000; // Convert to milliseconds
    const now = Date.now();
    const timeUntilExpiry = expiresAt - now;

    return timeUntilExpiry < thresholdMs;
  } catch (error) {
    authLogger.error('Error checking token expiry:', error);
    return false;
  }
}

/**
 * Proactively refresh token if it's expiring soon
 * Call this periodically or before important operations
 */
export async function refreshIfExpiringSoon(thresholdMs: number = 5 * 60 * 1000): Promise<void> {
  if (isTokenExpiringSoon(thresholdMs)) {
    authLogger.info('Token expiring soon, proactively refreshing...');
    await refreshAccessToken();
  }
}
