/**
 * test/helpers/waterlineMocks.js
 *
 * Helpers para imitar el comportamiento de Waterline dentro de tests
 * unitarios sin levantar una BD real.
 *
 * Waterline expone un objeto "Deferred" que es a la vez una Promise
 * (thenable) y una API encadenable con métodos como `.usingConnection()`,
 * `.fetch()`, `.populate()`, etc. Estas factorías producen mocks que
 * imitan ese comportamiento de manera mínima — solo lo necesario para
 * testear `DuplicatedMenuService`.
 */

/**
 * Construye un objeto encadenable y thenable que resuelve con `value`.
 *
 *   await Model.find({...})                          → value
 *   await Model.find({...}).usingConnection(db)      → value
 *   await Model.createEach(rows).fetch()             → value
 *   await Model.createEach(rows).fetch().usingConnection(db) → value
 *
 * @param {*} value Valor con el que la promesa final resuelve.
 * @returns {Object} chainable thenable.
 */
function makeChainable(value) {
  const promise = Promise.resolve(value);
  const chain = {
    usingConnection: jest.fn(() => chain),
    fetch:           jest.fn(() => chain),
    populate:        jest.fn(() => chain),
    sort:            jest.fn(() => chain),
    where:           jest.fn(() => chain),
    then:            (resolve, reject) => promise.then(resolve, reject),
    catch:           (reject)          => promise.catch(reject),
    finally:         (handler)         => promise.finally(handler)
  };
  return chain;
}

/**
 * Igual que `makeChainable`, pero la promesa final rechaza con `error`.
 * Útil para forzar un fallo en una operación específica y validar que
 * el error se propaga (rollback).
 *
 * @param {Error} error
 * @returns {Object} chainable thenable rejected.
 */
function makeRejectedChainable(error) {
  const promise = Promise.reject(error);
  // Evitamos UnhandledPromiseRejection si nadie lo consume todavía.
  promise.catch(() => { /* swallow */ });
  const chain = {
    usingConnection: jest.fn(() => chain),
    fetch:           jest.fn(() => chain),
    populate:        jest.fn(() => chain),
    sort:            jest.fn(() => chain),
    where:           jest.fn(() => chain),
    then:            (resolve, reject) => promise.then(resolve, reject),
    catch:           (reject)          => promise.catch(reject),
    finally:         (handler)         => promise.finally(handler)
  };
  return chain;
}

/**
 * Construye un mock mínimo de `sails` con un datastore cuya
 * `transaction(cb)` ejecuta el callback con un `db` falso y propaga
 * cualquier error/valor de retorno.
 *
 * Esto NO simula rollback real — para eso necesitarías una BD. Pero sí
 * verifica que:
 *   - Toda la lógica de mutación está dentro del callback.
 *   - Los errores propagados (incluido `DuplicatedMenuError`) salen del
 *     `transaction`, que es lo que la app espera para que el rollback
 *     real ocurra cuando corre contra MySQL.
 *
 * @returns {Object} sails mock + handle a la spy de `transaction`.
 */
function makeSailsMock() {
  const fakeDb = { __isFakeConnection: true };
  const transaction = jest.fn(async (cb) => cb(fakeDb));
  const datastore = { transaction };

  return {
    sails: {
      getDatastore: jest.fn(() => datastore),
      log: {
        error: jest.fn(),
        warn:  jest.fn(),
        info:  jest.fn(),
        debug: jest.fn(),
        verbose: jest.fn()
      },
      config: {}
    },
    fakeDb,
    transaction,
    datastore
  };
}

/**
 * Crea un set completo de globals que el service usa, con todos los
 * métodos como `jest.fn()` listos para configurar por test.
 *
 * Después de llamar esto, los tests pueden hacer:
 *
 *   User.findOne.mockResolvedValue({ id: 1 });
 *   RestaurantMenu.find.mockReturnValue(makeChainable([{ id: 10 }]));
 *
 * @returns {Object} con referencias a cada modelo mock.
 */
function makeModelMocks() {
  const User                       = { findOne: jest.fn() };
  const Restaurant                 = { findOne: jest.fn() };
  const RestaurantMenu             = { find: jest.fn(), createEach: jest.fn() };
  const RestaurantMenuItem         = { find: jest.fn(), createEach: jest.fn() };
  const Restaurantmenuextrasection = { find: jest.fn(), createEach: jest.fn() };
  const RestaurantMenuExtraItem    = { find: jest.fn(), createEach: jest.fn() };

  return {
    User,
    Restaurant,
    RestaurantMenu,
    RestaurantMenuItem,
    Restaurantmenuextrasection,
    RestaurantMenuExtraItem
  };
}

module.exports = {
  makeChainable,
  makeRejectedChainable,
  makeSailsMock,
  makeModelMocks
};
