import { WrapperObject } from '../BaseObjects/WrapperObject.js';
import { MelEnumerable } from '../classes/enum.js';
import {
  DATE_FORMAT,
  DATE_SERVER_FORMAT,
  DATE_TIME_FORMAT,
} from '../constants/constants.dates.js';
import { EMPTY_STRING } from '../constants/constants.js';
import { MelObject } from '../mel_object.js';
import { CalendarLoader } from './calendar_loader.js';
import { Slots } from './event/parts/guestspart.free_busy.js';
export { wrapper as FreeBusyLoader };

/**
 * Contient la classe qui permet de récupérer les informations de status de participants
 * @module FreeBusy
 */

/**
 * Clé qui permet de retrouver les dates des dernières mises à jour des status.
 * @constant
 * @package
 * @type {string}
 * @default 'wsp-freebusy-date-'
 */
const KEY_FREEBUSY_DATE = 'wsp-freebusy-date-';

/**
 * @class
 * @classdesc Gère, sauvegarder et récupère les données de status de participants entre deux dates.
 * @package
 * @extends MelObject
 */
class FreeBusyClassLoader extends MelObject {
  constructor() {
    super();
  }

  /**
   * Fonction principale de la classe
   */
  main() {
    super.main();

    this.interval = null;
    Object.defineProperty(this, 'interval', {
      get: () => {
        return (
          60 /
          (this.get_env('calendar_settings')?.timeslots ||
            this.rcmail(true).env.calendar_settings?.timeslots ||
            1)
        );
      },
    });
  }

  /**
   * Charge les statuts des utilisateurs
   * @generator
   * @async
   * @param {string[]} users Utilisateurs que l'on souhaite récupérer le statut
   * @param {number} interval Interval en minute
   * @param {external:moment} sd Date de début
   * @param {external:moment} ed Date de fin
   * @yields {module:EventView/Parts/Guests/FreeBusy.Slots}
   * @returns {AsyncGenerator<Slots>}
   * @frommodulereturn EventView/Parts/Guests/FreeBusy {@linkto Slots} {@membertype .}
   * @package
   */
  async *_load_free_busy(users, interval, sd, ed) {
    var promises = [];
    for (const user of users) {
      promises.push(
        new Promise((ok) => {
          this.http_call({
            url: this._call(user, interval, sd, ed),
            method: 'GET',
            on_success: (d) => {
              if (typeof d === 'string') d = JSON.parse(d);
              ok(new Slots(d));
            },
          });
        }),
      );
    }

    let results = await Promise.all(promises);

    for (const iterator of results) {
      yield iterator;
    }
  }

  /**
   * Récupère la bonne url pour récupérer le status
   * @param {string} user Email de l'utilisateur
   * @param {number} interval Interval en minute
   * @param {external:moment} sd Date de début
   * @param {external:moment} ed Date de fin
   * @returns {string}
   * @package
   */
  _call(user, interval, sd, ed) {
    return this.url('calendar', {
      action: 'freebusy-times',
      params: {
        interval,
        email: user,
        start: sd
          .format(DATE_SERVER_FORMAT)
          .replaceAll('P', 'T')
          .replaceAll('M', 'T'),
        end: ed
          .format(DATE_SERVER_FORMAT)
          .replaceAll('P', 'T')
          .replaceAll('M', 'T'),
        _remote: 1,
      },
    });
  }

  /**
   * Sauvegarde les données en mémoire pour un interval, des utilisateurs et une date donnée
   * @param {Slots[]} slots Données à sauvegarder
   * @param {string[]} users Emails des utilisateurs que l'on souhaite sauvegarder
   * @param {number} interval Interval en minute
   * @param {external:moment} start Date de départ
   * @frommoduleparam EventView/Parts/Guests/FreeBusy slots {@linkto Slots} {@membertype .}
   * @package
   */
  _save(slots, users, interval, start) {
    const key = 'free_busy';
    const current_key = `${users.join(EMPTY_STRING)}${interval}${start.format(DATE_FORMAT)}`;

    let data = this.load(key, {});
    data[current_key] = MelEnumerable.from(slots)
      .select((x) => x.serialize())
      .toArray();

    this._save_date(current_key);
    this.save(key, data);
  }

  /**
   * Vérifie si la date en mémoire est antérieur à la dernière date de chargement de l'agenda
   * @param {string} key Clé de la données
   * @returns {boolean}
   * @package
   */
  _is_date_ok(key) {
    const date = this.load(`${KEY_FREEBUSY_DATE}${key}`);

    if (date) {
      const agenda_date = this.load(CalendarLoader.Class.KEY_LAST_REFRESH);

      if (agenda_date) {
        const isok =
          moment(agenda_date, DATE_TIME_FORMAT) >
          moment(date, DATE_TIME_FORMAT);

        this.unload(key);
        return isok;
      }
    }

    return false;
  }

  /**
   * Sauvegarde la date actuelle pour une clé donnée
   * @param {string} key Clé de la donnée à sauvegardée
   * @package
   */
  _save_date(key) {
    this.save(`${KEY_FREEBUSY_DATE}${key}`, moment().format(DATE_TIME_FORMAT));
  }

  /**
   * Charge les données en mémoire pour des utilisateurs, une date et un interval donnée.
   * @param {string[]} users Adresse email des utilisateurs à charger
   * @param {number} interval Interval en minutes
   * @param {external:moment} start Date de départ
   * @returns {Object<string, Slots>} string => Email de l'utilisateur
   * @frommodulereturn EventView/Parts/Guests/FreeBusy {@linkto Slots} {@membertype .}
   * @package
   */
  _load(users, interval, start) {
    var return_data = {};

    const key = 'free_busy';
    const current_key = `${users.join(EMPTY_STRING)}${interval}${start.format(DATE_FORMAT)}`;

    if (this._is_date_ok(current_key)) {
      let data = this.load(key, {})[current_key] || [];

      for (const iterator of data) {
        return_data[iterator.email] = Slots.Deserialize(iterator);
      }
    }

    return return_data;
  }

  /**
   * Génère et récupère les données de statuts par utilisateurs
   * @param {string[]} users Adresse email des utilisateurs
   * @param {Object} param1
   * @param {number} param1.interval Interval en minutes (30 minutes par défaut)
   * @param {external:moment} param1.start Date de départ (Date de maintenant par défaut)
   * @param {external:moment} param1.end Date de fin (Date de maintenant + 7 jours par défaut)
   * @returns {AsyncGenerator<Slots>}
   * @frommodulereturn EventView/Parts/Guests/FreeBusy {@linkto Slots} {@membertype .}
   */
  async *generate(
    users,
    { interval = 30, start = moment(), end = moment().add(7, 'days') },
  ) {
    yield* this._load_free_busy(users, interval, start, end);
  }

  /**
   * Génère et récupère les données de statuts par utilisateurs.
   *
   * Sauvegarde en mémoire les données récupérés.
   * @param {string[]} users Adresse email des utilisateurs
   * @param {Object} param1
   * @param {number} param1.interval Interval en minutes (30 minutes par défaut)
   * @param {external:moment} param1.start Date de départ (Date de maintenant par défaut)
   * @param {external:moment} param1.end Date de fin (Date de maintenant + 7 jours par défaut)
   * @param {boolean} param1.save true par défaut
   * @returns {AsyncGenerator<Slots>}
   * @frommodulereturn EventView/Parts/Guests/FreeBusy {@linkto Slots} {@membertype .}
   */
  async *generate_and_save(
    users,
    {
      interval = 30,
      start = moment(),
      end = moment().add(7, 'days'),
      save = true,
    },
  ) {
    let data = [];
    for await (const iterator of this.generate(users, {
      interval,
      start,
      end,
    })) {
      data.push(iterator);
      yield iterator;
    }

    if (save) this._save(data, users, interval, start);
  }

  /**
   * Récupère les données de status d'utilisateurs
   * @param {string[]} users Emails des utilisateurs
   * @param {Object} param1
   * @param {number} param1.interval Interval en minutes (30 minutes par défaut)
   * @param {external:moment} param1.start Date de départ (Date de maintenant par défaut)
   * @param {external:moment} param1.end Date de fin (Date de maintenant + 7 jours par défaut)
   * @returns {Promise<Object<string, Slots>>}
   * @frommodulereturn EventView/Parts/Guests/FreeBusy {@linkto Slots} {@membertype .}
   */
  async get(
    users,
    { interval = 30, start = moment(), end = moment().add(7, 'days') },
  ) {
    var iterator;
    let data = {};

    for await (iterator of this._load_free_busy(users, interval, start, end)) {
      data[iterator.email] = iterator;
    }

    this._save(
      MelEnumerable.from(data)
        .select((x) => x.value)
        .toArray(),
      users,
      interval,
      start,
    );

    return data;
  }

  /**
   * Charge les données en mémoire d'utilisateurs.
   * @param {string[]} users Email des utilisateurs
   * @param {number} interval Interval en minute
   * @param {external:moment} start Date de début
   * @returns {Object<string, Slots>}
   * @frommodulereturn EventView/Parts/Guests/FreeBusy {@linkto Slots} {@membertype .}
   */
  load_from_memory(users, interval, start) {
    return this._load(users, interval, start);
  }

  /**
   * Supprime les données dans le stockages local lié aux status
   */
  clear_in_memory() {
    this.save('free_busy', {});
  }
}

// /**
//  * Instance de FreeBusyClassLoader
//  * @package
//  * @type {FreeBusyClassLoader}
//  * @frommodule FreeBusy
//  */
// let _fb_instance = null;

/**
 * Contient une instance de FreeBusyClassLoader
 * @alias FreeBusyLoader
 * @type {WrapperObject<FreeBusyClassLoader>}
 * @frommodule FreeBusy {@linkto FreeBusyClassLoader}
 * @member FreeBusy
 * @public
 */
let wrapper = WrapperObject.Create(FreeBusyClassLoader);
// let wrapper = {
//   /**
//    * @type {FreeBusyLoader}
//    * @readonly
//    * @frommodule FreeBusy
//    */
//   Instance: null,
// };

// Object.defineProperty(wrapper, 'Instance', {
//   get() {
//     if (!_fb_instance) _fb_instance = new FreeBusyClassLoader();

//     return _fb_instance;
//   },
// });