import axios, { AxiosResponse } from 'axios';
import { handleErrors } from '../lib/shared/errors/handleErrors';
import { IHttpError } from '@/interfaces/shared/lib/errors/IHttpError';
import { IServerResponse } from '@/interfaces/shared/services/httpService/IServerResponse';
import { IHttpOptions } from '@/interfaces/shared/services/httpService/IHttpOptions';
import { showSuccess, showWarning } from '@/lib/shared/message/functions';

/**
 * Service HTTP simplifié pour les appels API
 *
 * Exemple :
 * const data = await HttpService.getData<RolesResumeData>(
 *  'endPoint',
 *  {
 *    errorCodes: [403, 500],
 *    defaultErrorMessage: t('roles.resume.errorLoading', 'Erreur lors du chargement du résumé des rôles')
 *  }
 * );
 */
export class HttpService {
  /**
   * Méthode POST qui ne retourne qu'un message de succès
   * Utile pour les actions comme créer, modifier, supprimer
   *
   * @param url - URL de l'endpoint
   * @param data - Données à envoyer
   * @param options - Options pour la gestion d'erreur
   * @returns Promise<string> - Message de succès ou null si échec
   */
  static async postMessage(
    url: string,
    data?: any,
    options: IHttpOptions = {}
  ): Promise<string | null> {
    try {
      const response: AxiosResponse<IServerResponse> = await axios.post(url, data);

      if (response.status === 200 || response.status === 201) {
        return response.data.message || 'Opération réussie';
      }

      return null;
    } catch (error: any) {
      handleErrors(
        error as IHttpError,
        options.errorCodes || [401, 404, 422, 500],
        options.defaultErrorMessage || 'Erreur lors de l\'opération'
      );
      return null;
    }
  }

  /**
   * Méthode POST qui retourne des données typées
   *
   * @param url - URL de l'endpoint
   * @param data - Données à envoyer
   * @param options - Options pour la gestion d'erreur
   * @param throwOnError - Si true, rejette l'erreur pour la capturer dans le composant
   * @returns Promise<T | null> - Données typées ou null si échec
   */
  static async postData<T>(
    url: string,
    data?: any,
    options: IHttpOptions = {},
    throwOnError: boolean = false
  ): Promise<T | null> {
    try {
      const response: AxiosResponse<IServerResponse<T>> = await axios.post(url, data);

      if (response.status === 200 || response.status === 201) {
        return response.data.data || (response.data as T);
      }

      return null;

    } catch (error: any) {
      const status = error.response?.status;

      if (status === 401) {
        await HttpService.handleUnauthorizedError();
        if (throwOnError) throw error;
        return null;
      }

      if (status === 429) {
        handleErrors(error as IHttpError, [429], "Trop de tentatives. Veuillez patienter avant de réessayer.");
        if (throwOnError) throw error; 
        return null;
      }

      if (status === 403) {
        handleErrors(error as IHttpError, [403], "Vous n’avez pas les droits nécessaires pour effectuer cette action.");
        if (throwOnError) throw error; 
        return null;
      }

      handleErrors(
        error as IHttpError,
        options.errorCodes || [401, 404, 422, 500],
        options.defaultErrorMessage || 'Erreur lors de l\'opération'
      );

      if (throwOnError) throw error; 
      return null;
    }
  }

  /**
   * Méthode GET dédiée à la récupération de fichiers (Blob)
   * Indispensable pour télécharger proprement des PDF, images, etc. sans corrompre l'encodage.
   *
   * @param url - URL de l'endpoint
   * @param options - Options pour la gestion d'erreur (params, errorCodes, etc.)
   * @returns Promise<Blob | null> - Le fichier sous forme de Blob, ou null si échec
   */
  static async getBlob(
    url: string,
    options: IHttpOptions = {}
  ): Promise<Blob | null> {
    try {
      const axiosConfig: any = {
        responseType: 'blob', // ⚠️ CRUCIAL: Indique à Axios de garder les données sous forme binaire
      };

      if (options.params) {
        axiosConfig.params = options.params;
      }

      const response: AxiosResponse<Blob> = await axios.get(url, axiosConfig);

      if (response.status === 200 || response.status === 201) {
        return response.data;
      }

      return null;
    } catch (error: any) {
      if (options.silent) return null;

      const status = error?.response?.status;

      // Si le serveur renvoie une erreur (ex: 404 ou 403), il renvoie souvent du JSON.
      // À cause de `responseType: 'blob'`, Axios transforme cette erreur JSON en Blob.
      // On retransforme le Blob en JSON pour que `handleErrors` puisse lire le vrai message d'erreur !
      if (
        error.response?.data instanceof Blob && 
        error.response.data.type === 'application/json'
      ) {
        try {
          const textData = await error.response.data.text();
          error.response.data = JSON.parse(textData);
        } catch (e) {
          // Si la conversion échoue, on ne fait rien, on garde le Blob d'erreur
        }
      }

      if (status === 401) {
        await HttpService.handleUnauthorizedError();
        return null;
      }

      if (status === 403) {
        // On vérifie si un message d'erreur custom a pu être extrait
        const serverMessage = error.response?.data?.message || 'Vous n\'avez pas accès à ce fichier.';
        showWarning(serverMessage);
      } else {
        // Autres erreurs classiques
        handleErrors(
          error as IHttpError,
          options.errorCodes || [401, 403, 404, 500],
          options.defaultErrorMessage || 'Erreur lors du téléchargement du fichier'
        );
      }
      
      return null;
    }
  }

  /**
   * Méthode POST dédiée à la récupération de fichiers (Blob)
   */
  static async postBlob(
    url: string,
    data?: any,
    options: IHttpOptions = {}
  ): Promise<Blob | null> {
    try {
      const axiosConfig: any = {
        responseType: 'blob',
        params: options.params
      };

      // Si data est un objet simple, postDataWithFiles s'en chargerait, 
      // mais ici on gère le blob de réponse spécifiquement.
      const response: AxiosResponse<Blob> = await axios.post(url, data, axiosConfig);

      if (response.status === 200 || response.status === 201) {
        return response.data;
      }
      return null;
    } catch (error: any) {
      // Gestion des erreurs identique à getBlob (retransformation du blob JSON en objet)
      if (error.response?.data instanceof Blob && error.response.data.type === 'application/json') {
        const textData = await error.response.data.text();
        error.response.data = JSON.parse(textData);
      }
      handleErrors(error as IHttpError, options.errorCodes || [401, 403, 404, 500], 'Erreur lors de la génération du fichier');
      return null;
    }
  }
  
  /**
   * Méthode GET qui retourne des données typées
   *
   * @param url - URL de l'endpoint
   * @param options - Options pour la gestion d'erreur
   * @returns Promise<T | null> - Données typées ou null si échec
   */
  static async getData<T>(
    url: string,
    options: IHttpOptions = {}
  ): Promise<IServerResponse<T> | null> {
    try {
      const axiosConfig = options.params ? { params: options.params } : undefined;

      const response: AxiosResponse<IServerResponse<T>> = await axios.get(url, axiosConfig);

      if (response.status === 200 || response.status === 201) {
        if (options.raw) {
          return response.data; // retourne tout
        }

        // retourne tout le IServerResponse<T>
        return response.data;
      }

      return null;
    } catch (error: any) {
      if (options.silent) return null;
    
      const status = error?.response?.status;

      if (status === 401) {
        await HttpService.handleUnauthorizedError();
        return null;
      }

      if (status === 403) {
        showWarning('Vous n\'avez pas accès à cette ressource.');
      } else {
        // Autres erreurs classiques
        handleErrors(
          error as IHttpError,
          options.errorCodes || [401, 404, 422, 500],
          options.defaultErrorMessage || 'Erreur lors de la récupération des données'
        );
      }
      return null;
    }
  }

  /**
   * Méthode GET qui retourne des données typées et Paginé
   *
   * @param url - URL de l'endpoint
   * @param options - Options pour la gestion d'erreur
   * @returns Promise<T | null> - Données typées ou null si échec
   */
  static async getDataPag<T>(
    url: string,
    options: IHttpOptions = {}
  ): Promise<T | null> {
    try {
      const axiosConfig = options.params ? { params: options.params } : undefined;

      const response: AxiosResponse<any> = await axios.get(url, axiosConfig);

      if (response.status === 200 || response.status === 201) {
        if (options.raw) {
          return response.data as T; 
        }

        // retourne tout le IServerResponse<T>
        return response.data;
      }

      return null;
    } catch (error: any) {
      const status = error?.response?.status;

      if (status === 401) {
        await HttpService.handleUnauthorizedError();
        return null;
      }

      if (status === 403) {
        showWarning('Vous n\'avez pas accès à cette ressource.');
      } else {
        // Autres erreurs classiques
        handleErrors(
          error as IHttpError,
          options.errorCodes || [401, 404, 422, 500],
          options.defaultErrorMessage || 'Erreur lors de la récupération des données'
        );
      }
      return null;
    }
  }

  /**
   * Méthode PUT qui ne retourne qu'un message de succès
   *
   * @param url - URL de l'endpoint
   * @param data - Données à envoyer
   * @param options - Options pour la gestion d'erreur
   * @returns Promise<string | null> - Message de succès ou null si échec
   */
  static async putData(
    url: string,
    data?: any,
    options: IHttpOptions = {}
  ): Promise<string | null> {
    try {
      const response: AxiosResponse<IServerResponse> = await axios.put(url, data);

      if (response.status === 200 || response.status === 201) {
        return response.data.message || 'Modification réussie';
      }

      return null;
    } catch (error: any) {
      const status = error.response?.status;

      // 💡 GESTION DU 401
      if (status === 401) {
        await HttpService.handleUnauthorizedError();
        return null;
      }

      if (status === 403) {
        // Récupération dynamique du message
        const resData = error.response?.data;
        // Priorité : message > error > errors > Message par défaut
        const serverMessage = resData?.message 
          || resData?.error 
          || (Array.isArray(resData?.errors) ? resData.errors.join(', ') : resData?.errors) 
          || "Vous n’avez pas les droits nécessaires pour effectuer cette action.";

        handleErrors(
          error as IHttpError,
          [403],
          serverMessage
        );
        return null;
      }

      handleErrors(
        error as IHttpError,
        options.errorCodes || [401, 404, 422, 500],
        options.defaultErrorMessage || 'Erreur lors de la modification'
      );
      return null;
    }
  }

  /**
   * Méthode DELETE qui ne retourne qu'un message de succès
   *
   * @param url - URL de l'endpoint
   * @param options - Options pour la gestion d'erreur
   * @returns Promise<string | null> - Message de succès ou null si échec
   */
  static async deleteData(
    url: string,
    options: IHttpOptions = {}
  ): Promise<string | null> {
    try {
      const response: AxiosResponse<IServerResponse> = await axios.delete(url, {
        data: options.params 
      });

      if (response.status === 200 || response.status === 201) {
        return response.data.message || 'Suppression réussie';
      }

      return null;
    } catch (error: any) {
      const status = error.response?.status;

      if (status === 401) {
        await HttpService.handleUnauthorizedError();
        return null;
      }

      if (status === 403) {
        // Récupération dynamique du message
        const resData = error.response?.data;
        
        // 1. On cherche 'message' (standard Laravel/Axios)
        // 2. Sinon 'error'
        // 3. Sinon 'errors' (si c'est un tableau, on joint les erreurs)
        // 4. Sinon le message par défaut
        const serverMessage = resData?.message 
          || resData?.error 
          || (Array.isArray(resData?.errors) ? resData.errors.join(', ') : resData?.errors)
          || "Vous n’avez pas les droits nécessaires pour effectuer cette action.";

        handleErrors(
          error as IHttpError,
          [403],
          serverMessage
        );
        return null;
      }
      
      handleErrors(
        error as IHttpError,
        options.errorCodes || [401, 404, 422, 500],
        options.defaultErrorMessage || 'Erreur lors de la suppression'
      );
      return null;
    }
  }

  /**
   * Méthode POST qui gère automatiquement les fichiers et les données
   * Convertit automatiquement les File/File[] en FormData
   * 
   * @param url - URL de l'endpoint
   * @param data - Données à envoyer (peut contenir des File ou File[])
   * @param options - Options pour la gestion d'erreur
   * @returns Promise<T | null> - Données typées ou null si échec
   * 
   * Exemple d'utilisation :
   * const response = await HttpService.postDataWithFiles<IServerResponse<ReferenceForm>>(
   *   '/app/stocks/reference/create',
   *   { 
   *     name: 'Produit',
   *     images: [file1, file2],  // File[]
   *     photo: file3,             // File
   *     description: 'texte'
   *   },
   *   { errorCodes: [401, 404, 422, 500] }
   * );
   */
  static async postDataWithFiles<T>(
    url: string,
    data?: any,
    options: IHttpOptions = {}
  ): Promise<T | null> {
    try {
      const formData = new FormData();
      const hasFiles = HttpService.appendToFormData(formData, data);

      // Si pas de fichiers, utiliser postData classique
      if (!hasFiles) {
        return await HttpService.postData<T>(url, data, options);
      }

      // Envoyer avec FormData
      const response: AxiosResponse<IServerResponse<T>> = await axios.post(url, formData, {
        headers: {
          'Content-Type': 'multipart/form-data',
        },
      });

      if (response.status === 200 || response.status === 201) {
        return response.data.data || response.data as T;
      }

      return null;
    } catch (error: any) {
      const status = error.response?.status;

      if (status === 401) {
        await HttpService.handleUnauthorizedError();
        return null;
      }
      
      if (status === 429) {
        handleErrors(
          error as IHttpError,
          [429],
          "Trop de tentatives. Veuillez patienter avant de réessayer."
        );
        return null;
      }

      if (status === 403) {
        handleErrors(
          error as IHttpError,
          [403],
          "Vous n’avez pas les droits nécessaires pour effectuer cette action."
        );
        return null;
      }

      handleErrors(
        error as IHttpError,
        options.errorCodes || [401, 404, 422, 500],
        options.defaultErrorMessage || 'Erreur lors de l\'opération'
      );

      return null;
    }
  }

  /**
   * Ne fonctionne pas avec les fichiers.
   * Méthode PUT qui gère automatiquement les fichiers et les données
   * 
   * @param url - URL de l'endpoint
   * @param data - Données à envoyer (peut contenir des File ou File[])
   * @param options - Options pour la gestion d'erreur
   * @returns Promise<string | null> - Message de succès ou null si échec
   */
  static async putDataWithFiles(
    url: string,
    data?: any,
    options: IHttpOptions = {}
  ): Promise<string | null> {
    try {
      const formData = new FormData();
      const hasFiles = HttpService.appendToFormData(formData, data);
      
      // Si pas de fichiers, utiliser putData classique
      if (!hasFiles) {
        return await HttpService.putData(url, data, options);
      }

      // Envoyer avec FormData
      const response: AxiosResponse<IServerResponse> = await axios.put(url, formData, {
        headers: {
          'Content-Type': 'multipart/form-data',
        },
      });

      if (response.status === 200 || response.status === 201) {
        return response.data.message || 'Modification réussie';
      }

      return null;
    } catch (error: any) {
      handleErrors(
        error as IHttpError,
        options.errorCodes || [401, 404, 422, 500],
        options.defaultErrorMessage || 'Erreur lors de la modification'
      );
      return null;
    }
  }

  /**
   * Méthode privée pour convertir un objet en FormData
   * Gère automatiquement les File, File[], et les données primitives.
   * Gère également les images imbriqués dans des objets.
   * 
   * @param formData - Instance FormData à remplir
   * @param data - Données à convertir
   * @param parentKey - Clé parente pour les objets imbriqués
   * @returns boolean - true si des fichiers ont été trouvés
   */
  private static appendToFormData(
    formData: FormData,
    data: any,
    parentKey: string = ''
  ): boolean {
    let hasFiles = false;

    if (data === null || data === undefined) {
      return false;
    }

    // Si c'est un File
    if (data instanceof File) {
      formData.append(parentKey, data);
      return true;
    }

    // Si c'est un tableau
    if (Array.isArray(data)) {
      data.forEach((item, index) => {
        const key = parentKey ? `${parentKey}[${index}]` : `${index}`;
        const foundFile = HttpService.appendToFormData(formData, item, key);
        if (foundFile) hasFiles = true;
      });
      return hasFiles;
    }

    // Si c'est un objet (mais pas File, pas Date)
    if (typeof data === 'object' && !(data instanceof Date)) {
      Object.entries(data).forEach(([key, value]) => {
        const formKey = parentKey ? `${parentKey}[${key}]` : key;
        const foundFile = HttpService.appendToFormData(formData, value, formKey);
        if (foundFile) hasFiles = true;
      });
      return hasFiles;
    }

    // Valeurs primitives
    if (data instanceof Date) {
      formData.append(parentKey, data.toISOString());
    } else {
      formData.append(parentKey, String(data));
    }

    return hasFiles;
  }

  /**
   * Méthode privée pour gérer spécifiquement le cas du 401 (Non autorisé)
   * Informe Amedya de purger la session locale (SQLite) puis redirige vers le login.
   */
  private static async handleUnauthorizedError(): Promise<void> {
    try {
      // 1. On informe le backend local Amedya de détruire la session SQLite
      await axios.post('/auth/force-logout');
    } catch (e) {
      // On ignore l'erreur si le backend Amedya est déjà déconnecté
    } finally {
      // 2. Redirection forcée vers l'écran de connexion
      window.location.href = '/login';
    }
  }
}

