import { Injectable } from "@angular/core";
import { BehaviorSubject, Subject } from "rxjs";

interface CacheEntry {
  data: any;
  timestamp: number;
  expiresAt: number;
}

interface LoginAttempt {
  username: string;
  timestamp: number;
  successful: boolean;
}

@Injectable({
  providedIn: "root",
})
export class LoginOptimizationService {
  private readonly CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
  private readonly MAX_ATTEMPTS = 3;
  private readonly BLOCK_DURATION = 5 * 60 * 1000; // 5 minutes
  private readonly ATTEMPT_WINDOW = 15 * 60 * 1000; // 15 minutes

  private cache = new Map<string, CacheEntry>();
  private loginAttempts: LoginAttempt[] = [];

  // Sujet pour notifier les composants des changements d'état
  private loginState$ = new BehaviorSubject<{
    isBlocked: boolean;
    attemptsRemaining: number;
    blockTimeRemaining: number;
  }>({
    isBlocked: false,
    attemptsRemaining: this.MAX_ATTEMPTS,
    blockTimeRemaining: 0,
  });

  constructor() {
    this.loadPersistedData();
    this.startCleanupTimer();
    this.startBlockTimer();
  }

  // Getters pour les observables
  get loginStateChanges() {
    return this.loginState$.asObservable();
  }

  /**
   * Met en cache des données avec expiration automatique
   */
  setCacheData(key: string, data: any, customDuration?: number): void {
    // Éviter de mettre en cache des données nulles ou undefined
    if (data === null || data === undefined) {
      console.warn(`⚠️ Tentative de mise en cache de données nulles pour la clé: ${key}`);
      return;
    }
    
    const duration = customDuration || this.CACHE_DURATION;
    const entry: CacheEntry = {
      data,
      timestamp: Date.now(),
      expiresAt: Date.now() + duration,
    };

    this.cache.set(key, entry);

    // Persister les données importantes
    if (key === "menus" || key === "user-permissions") {
      try {
        localStorage.setItem(`cache_${key}`, JSON.stringify(entry));
      } catch (error) {
        console.warn(`⚠️ Erreur lors de la sauvegarde en localStorage pour ${key}:`, error);
      }
    }
  }

  /**
   * Récupère des données du cache si elles sont encore valides
   */
  getCacheData(key: string): any | null {
    const entry = this.cache.get(key);

    if (!entry) {
      // Essayer de récupérer depuis localStorage pour les données importantes
      const stored = localStorage.getItem(`cache_${key}`);
      if (stored) {
        try {
          const parsedEntry: CacheEntry = JSON.parse(stored);
          if (Date.now() < parsedEntry.expiresAt) {
            this.cache.set(key, parsedEntry);
            return parsedEntry.data;
          } else {
            localStorage.removeItem(`cache_${key}`);
          }
        } catch (error) {
          console.warn(`Erreur lors du parsing du cache ${key}:`, error);
        }
      }
      return null;
    }

    if (Date.now() > entry.expiresAt) {
      this.cache.delete(key);
      localStorage.removeItem(`cache_${key}`);
      return null;
    }

    return entry.data;
  }

  /**
   * Précharge les données essentielles en arrière-plan
   */
  async prefetchEssentialData(userService: any): Promise<void> {
    try {
      // Précharger les menus si pas en cache
      if (!this.getCacheData("menus")) {
        const menuPromise = userService.getAllElementsWithAuth("v1/menus");
        menuPromise
          .then((data: any) => {
            if (data?.data) {
              this.setCacheData("menus", data);
            }
          })
          .catch(() => {
            // Échec silencieux pour le prefetch
          });
      }

      // Vous pouvez ajouter d'autres préchargements ici
    } catch (error) {
      console.warn("Erreur lors du préchargement:", error);
    }
  }

  /**
   * Enregistre une tentative de connexion
   */
  recordLoginAttempt(username: string, successful: boolean): void {
    const attempt: LoginAttempt = {
      username: username.toLowerCase(),
      timestamp: Date.now(),
      successful,
    };

    this.loginAttempts.push(attempt);
    this.persistLoginAttempts();
    this.updateLoginState();

    // Nettoyer les anciennes tentatives
    this.cleanOldAttempts();
  }

  /**
   * Vérifie si l'utilisateur est bloqué
   */
  isUserBlocked(username: string): boolean {
    const userAttempts = this.getRecentFailedAttempts(username);
    return userAttempts.length >= this.MAX_ATTEMPTS;
  }

  /**
   * Obtient le temps de blocage restant en secondes
   */
  getBlockTimeRemaining(username: string): number {
    const userAttempts = this.getRecentFailedAttempts(username);
    if (userAttempts.length < this.MAX_ATTEMPTS) {
      return 0;
    }

    const lastAttempt = userAttempts[userAttempts.length - 1];
    const blockEndTime = lastAttempt.timestamp + this.BLOCK_DURATION;
    const remaining = Math.max(0, blockEndTime - Date.now());

    // Si le temps est écoulé, nettoyer les tentatives
    if (remaining <= 0) {
      this.resetAttempts(username);
      return 0;
    }

    return Math.ceil(remaining / 1000);
  }

  /**
   * Obtient le nombre de tentatives restantes
   */
  getAttemptsRemaining(username: string): number {
    const userAttempts = this.getRecentFailedAttempts(username);
    return Math.max(0, this.MAX_ATTEMPTS - userAttempts.length);
  }

  /**
   * Réinitialise les tentatives pour un utilisateur (après connexion réussie)
   */
  resetAttempts(username: string): void {
    this.loginAttempts = this.loginAttempts.filter(
      (attempt) => attempt.username !== username.toLowerCase()
    );
    this.persistLoginAttempts();
    this.updateLoginState();
  }

  /**
   * Nettoie tout le cache et les données stockées
   */
  clearAllData(): void {
    this.cache.clear();
    this.loginAttempts = [];

    // Nettoyer localStorage
    const keysToRemove = [];
    for (let i = 0; i < localStorage.length; i++) {
      const key = localStorage.key(i);
      if (key && (key.startsWith("cache_") || key === "loginAttempts")) {
        keysToRemove.push(key);
      }
    }
    keysToRemove.forEach((key) => localStorage.removeItem(key));
  }

  /**
   * Optimise les performances en nettoyant périodiquement
   */
  private startCleanupTimer(): void {
    setInterval(() => {
      this.cleanExpiredCache();
      this.cleanOldAttempts();
    }, 60000); // Nettoyage toutes les minutes
  }

  private getRecentFailedAttempts(username: string): LoginAttempt[] {
    const cutoff = Date.now() - this.ATTEMPT_WINDOW;
    return this.loginAttempts.filter(
      (attempt) =>
        attempt.username === username.toLowerCase() &&
        !attempt.successful &&
        attempt.timestamp > cutoff
    );
  }

  private cleanOldAttempts(): void {
    const cutoff = Date.now() - this.ATTEMPT_WINDOW;
    this.loginAttempts = this.loginAttempts.filter(
      (attempt) => attempt.timestamp > cutoff
    );
    this.persistLoginAttempts();
  }

  private cleanExpiredCache(): void {
    const now = Date.now();
    for (const [key, entry] of this.cache.entries()) {
      if (now > entry.expiresAt) {
        this.cache.delete(key);
        localStorage.removeItem(`cache_${key}`);
      }
    }
  }

  private loadPersistedData(): void {
    // Charger les tentatives de connexion
    try {
      const stored = localStorage.getItem("loginAttempts");
      if (stored) {
        this.loginAttempts = JSON.parse(stored);
        this.cleanOldAttempts(); // Nettoyer au chargement
      }
    } catch (error) {
      console.warn("Erreur lors du chargement des tentatives:", error);
      this.loginAttempts = [];
    }
  }

  private persistLoginAttempts(): void {
    try {
      localStorage.setItem("loginAttempts", JSON.stringify(this.loginAttempts));
    } catch (error) {
      console.warn("Erreur lors de la sauvegarde des tentatives:", error);
    }
  }

  private updateLoginState(): void {
    // Mettre à jour l'état pour tous les utilisateurs (en se basant sur le plus restrictif)
    let isBlocked = false;
    let minAttemptsRemaining = this.MAX_ATTEMPTS;
    let maxBlockTime = 0;

    // Analyser tous les utilisateurs récents
    const recentUsernames = [
      ...new Set(
        this.loginAttempts
          .filter(
            (attempt) => attempt.timestamp > Date.now() - this.ATTEMPT_WINDOW
          )
          .map((attempt) => attempt.username)
      ),
    ];

    for (const username of recentUsernames) {
      const userBlocked = this.isUserBlocked(username);
      const attemptsRemaining = this.getAttemptsRemaining(username);
      const blockTime = this.getBlockTimeRemaining(username);

      if (userBlocked && blockTime > 0) {
        isBlocked = true;
      }

      minAttemptsRemaining = Math.min(minAttemptsRemaining, attemptsRemaining);
      maxBlockTime = Math.max(maxBlockTime, blockTime);
    }

    // Si aucun utilisateur n'est bloqué, s'assurer que isBlocked est false
    if (maxBlockTime <= 0) {
      isBlocked = false;
    }

    this.loginState$.next({
      isBlocked,
      attemptsRemaining: minAttemptsRemaining,
      blockTimeRemaining: maxBlockTime,
    });
  }

  private startBlockTimer(): void {
    // Mettre à jour l'état toutes les secondes pour les utilisateurs bloqués
    setInterval(() => {
      this.updateLoginState();
    }, 1000);
  }

  /**
   * Force le nettoyage des tentatives expirées (utile pour déboguer)
   */
  forceCleanup(): void {
    this.cleanOldAttempts();
    this.updateLoginState();
  }
}
