import axios, { AxiosInstance } from 'axios';
import { TokenStorage } from './tokenStorage';

// T0177: Créer un client axios séparé pour le refresh pour éviter les interceptors
// Lazy initialization pour faciliter les tests
let refreshClient: AxiosInstance | null = null;

// INT-016: Refresh proactif - rafraîchir 5 minutes avant expiration
const PROACTIVE_REFRESH_BUFFER_MS = 5 * 60 * 1000; // 5 minutes

// INT-016: Timer pour le refresh proactif
let proactiveRefreshTimer: ReturnType<typeof setTimeout> | null = null;

function getRefreshClient(): AxiosInstance {
  if (!refreshClient) {
    const baseURL = (() => {
      const url = import.meta.env.VITE_API_URL;
      if (!url) {
        if (import.meta.env.PROD) {
          throw new Error('VITE_API_URL must be defined in production');
        }
        // Fallback uniquement en développement
        return 'http://127.0.0.1:8080/api/v1';
      }
      return url;
    })();

    refreshClient = axios.create({
      baseURL,
      timeout: 10000,
      headers: {
        'Content-Type': 'application/json',
      },
    });
  }
  return refreshClient;
}

/**
 * TokenRefresh - Service de rafraîchissement des tokens d'authentification
 * T0176: Service pour rafraîchir les tokens via l'endpoint /auth/refresh
 * 
 * Format de réponse attendu du backend :
 * {
 *   "success": true,
 *   "data": {
 *     "access_token": "...",
 *     "refresh_token": "...",
 *     "expires_in": 3600
 *   }
 * }
 */

export interface RefreshTokenResponse {
  access_token: string;
  refresh_token: string;
  expires_in: number;
}

/**
 * INT-016: Décode un JWT pour extraire les claims (sans vérifier la signature)
 * Utilisé uniquement pour lire l'expiration, pas pour la validation de sécurité
 */
interface JWTPayload {
  exp?: number; // Expiration timestamp (seconds)
  iat?: number; // Issued at timestamp (seconds)
}

function decodeJWT(token: string): JWTPayload | null {
  try {
    const parts = token.split('.');
    if (parts.length !== 3) {
      return null;
    }
    // Décoder le payload (base64url)
    const payload = parts[1];
    const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
    return JSON.parse(decoded) as JWTPayload;
  } catch (error) {
    console.warn('Failed to decode JWT:', error);
    return null;
  }
}

/**
 * INT-016: Vérifie si un token est expiré ou proche de l'expiration
 * @param token - Token JWT à vérifier
 * @param bufferMs - Buffer en millisecondes avant expiration (défaut: 5 minutes)
 * @returns true si le token est expiré ou proche de l'expiration
 */
function isTokenExpiringSoon(token: string | null, bufferMs: number = PROACTIVE_REFRESH_BUFFER_MS): boolean {
  if (!token) {
    return true;
  }

  const payload = decodeJWT(token);
  if (!payload || !payload.exp) {
    // Si on ne peut pas décoder, on considère qu'il faut rafraîchir
    return true;
  }

  const expirationTime = payload.exp * 1000; // Convertir en millisecondes
  const now = Date.now();
  const timeUntilExpiration = expirationTime - now;

  // Rafraîchir si expiré ou si expiration dans moins de bufferMs
  return timeUntilExpiration <= bufferMs;
}

/**
 * Rafraîchit le token d'accès en utilisant le refresh token
 * T0176: Appelle l'endpoint POST /api/v1/auth/refresh et met à jour les tokens
 * @returns Promise qui se résout quand le token est rafraîchi
 * @throws Error si le refresh token n'est pas disponible ou si le refresh échoue
 */
export async function refreshToken(): Promise<void> {
  // T0176: Récupérer le refresh token depuis le stockage
  const refreshToken = TokenStorage.getRefreshToken();
  if (!refreshToken) {
    throw new Error('No refresh token available');
  }

  try {
    // T0176: Appeler l'endpoint POST /auth/refresh
    // T0177: Utiliser refreshClient pour éviter les interceptors (qui causeraient une boucle)
    const client = getRefreshClient();
    const response = await client.post<{
      success: boolean;
      data: RefreshTokenResponse;
    }>('/auth/refresh', {
      refresh_token: refreshToken,
    });

    // Le backend retourne toujours { success: true, data: { access_token, refresh_token, expires_in } }
    if (!response.data?.success || !response.data?.data) {
      throw new Error(
        `Invalid refresh response format. Expected { success: true, data: { access_token, refresh_token, expires_in } }, got: ${JSON.stringify(response.data)}`,
      );
    }

    const { access_token, refresh_token, expires_in } = response.data.data;

    if (!access_token || !refresh_token) {
      throw new Error(
        'Invalid refresh response: missing access_token or refresh_token',
      );
    }

    // T0176: Mettre à jour les tokens stockés
    TokenStorage.setTokens(access_token, refresh_token);

    // INT-016: Programmer le refresh proactif pour le nouveau token
    scheduleProactiveRefresh(access_token, expires_in);
  } catch (error) {
    // T0176: Gérer les erreurs - supprimer les tokens en cas d'échec
    TokenStorage.clearTokens();
    // INT-016: Annuler le refresh proactif en cas d'erreur
    cancelProactiveRefresh();
    throw error;
  }
}

/**
 * INT-016: Programme un refresh proactif avant l'expiration du token
 * @param _accessToken - Token d'accès actuel (non utilisé directement, récupéré depuis storage)
 * @param expiresIn - Durée de validité en secondes
 */
function scheduleProactiveRefresh(_accessToken: string, expiresIn: number): void {
  // Annuler le timer précédent s'il existe
  cancelProactiveRefresh();

  // Calculer le temps jusqu'au refresh proactif
  const expiresInMs = expiresIn * 1000;
  const refreshTime = Math.max(0, expiresInMs - PROACTIVE_REFRESH_BUFFER_MS);

  if (refreshTime <= 0) {
    // Le token expire bientôt, rafraîchir immédiatement
    refreshToken().catch((error) => {
      console.warn('Proactive token refresh failed:', error);
    });
    return;
  }

  // Programmer le refresh proactif
  proactiveRefreshTimer = setTimeout(() => {
    const currentToken = TokenStorage.getAccessToken();
    if (currentToken && isTokenExpiringSoon(currentToken, PROACTIVE_REFRESH_BUFFER_MS)) {
      refreshToken().catch((error) => {
        console.warn('Proactive token refresh failed:', error);
      });
    }
    proactiveRefreshTimer = null;
  }, refreshTime);
}

/**
 * INT-016: Annule le refresh proactif programmé
 */
function cancelProactiveRefresh(): void {
  if (proactiveRefreshTimer) {
    clearTimeout(proactiveRefreshTimer);
    proactiveRefreshTimer = null;
  }
}

/**
 * INT-016: Vérifie si le token actuel doit être rafraîchi et le rafraîchit si nécessaire
 * @returns Promise qui se résout si le token est valide ou a été rafraîchi
 */
export async function ensureValidToken(): Promise<void> {
  const accessToken = TokenStorage.getAccessToken();
  const refreshTokenValue = TokenStorage.getRefreshToken();

  if (!accessToken || !refreshTokenValue) {
    throw new Error('No tokens available');
  }

  // Si le token est expiré ou proche de l'expiration, le rafraîchir
  if (isTokenExpiringSoon(accessToken, PROACTIVE_REFRESH_BUFFER_MS)) {
    await refreshToken();
  } else {
    // Programmer le refresh proactif si pas déjà programmé
    const payload = decodeJWT(accessToken);
    if (payload?.exp) {
      const expiresIn = Math.max(0, payload.exp - Math.floor(Date.now() / 1000));
      scheduleProactiveRefresh(accessToken, expiresIn);
    }
  }
}

/**
 * INT-016: Initialise le système de refresh proactif
 * À appeler après un login ou un refresh réussi
 */
export function initializeProactiveRefresh(): void {
  const accessToken = TokenStorage.getAccessToken();
  if (!accessToken) {
    return;
  }

  const payload = decodeJWT(accessToken);
  if (payload?.exp) {
    const expiresIn = Math.max(0, payload.exp - Math.floor(Date.now() / 1000));
    scheduleProactiveRefresh(accessToken, expiresIn);
  }
}

/**
 * INT-016: Nettoie le système de refresh proactif
 * À appeler lors du logout
 */
export function cleanupProactiveRefresh(): void {
  cancelProactiveRefresh();
}
