/**
 * test/lib/asyncHandler.test.js
 *
 * Verifica el wrapper que envuelve acciones de Sails para delegar errores
 * a `res.domainError(err)` sin try/catch en cada controller.
 */

'use strict';

const path = require('path');

const HANDLER_PATH = path.resolve(__dirname, '..', '..', 'api', 'lib', 'asyncHandler.js');

describe('lib/asyncHandler', () => {
  let asyncHandler;
  let req;
  let res;

  beforeEach(() => {
    jest.resetModules();

    req = {};
    res = {
      domainError: jest.fn(),
      serverError: jest.fn(),
      status:      jest.fn().mockReturnThis(),
      json:        jest.fn().mockReturnThis(),
      headersSent: false
    };

    global.sails = { log: { error: jest.fn() } };

    asyncHandler = require(HANDLER_PATH);
  });

  afterEach(() => {
    delete global.sails;
  });

  it('lanza si lo invocan con algo que no es función', () => {
    expect(() => asyncHandler(null)).toThrow(TypeError);
    expect(() => asyncHandler('foo')).toThrow(TypeError);
    expect(() => asyncHandler(42)).toThrow(TypeError);
  });

  it('async fn que resuelve → return value pasa, no llama domainError', async () => {
    const inner = jest.fn().mockResolvedValueOnce('ok');
    const wrapped = asyncHandler(inner);

    const result = await wrapped(req, res);

    expect(result).toBe('ok');
    expect(inner).toHaveBeenCalledWith(req, res);
    expect(res.domainError).not.toHaveBeenCalled();
  });

  it('async fn que lanza error de dominio → res.domainError(err)', async () => {
    const err = Object.assign(new Error('No encontrado'), { statusCode: 404 });
    const wrapped = asyncHandler(jest.fn().mockRejectedValueOnce(err));

    await wrapped(req, res);

    expect(res.domainError).toHaveBeenCalledTimes(1);
    expect(res.domainError).toHaveBeenCalledWith(err);
  });

  it('async fn que lanza error inesperado → también va por res.domainError', async () => {
    const err = new TypeError('unexpected');
    const wrapped = asyncHandler(jest.fn().mockRejectedValueOnce(err));

    await wrapped(req, res);

    expect(res.domainError).toHaveBeenCalledWith(err);
  });

  it('sync fn que lanza también es manejado', async () => {
    const err = Object.assign(new Error('sync boom'), { statusCode: 422 });
    const wrapped = asyncHandler(() => { throw err; });

    await wrapped(req, res);

    expect(res.domainError).toHaveBeenCalledWith(err);
  });

  it('si headersSent=true, no intenta re-responder, solo loguea', async () => {
    res.headersSent = true;
    const err = new Error('demasiado tarde');
    const wrapped = asyncHandler(jest.fn().mockRejectedValueOnce(err));

    await wrapped(req, res);

    expect(res.domainError).not.toHaveBeenCalled();
    expect(global.sails.log.error).toHaveBeenCalled();
  });

  it('fallback a res.serverError si domainError no está cargado', async () => {
    delete res.domainError;
    const err = Object.assign(new Error('no domainError'), { statusCode: 404 });
    const wrapped = asyncHandler(jest.fn().mockRejectedValueOnce(err));

    await wrapped(req, res);

    expect(res.serverError).toHaveBeenCalledWith({
      success: false,
      message: 'no domainError'
    });
  });

  it('fallback a res.status() si tampoco hay serverError', async () => {
    delete res.domainError;
    delete res.serverError;
    const err = Object.assign(new Error('último recurso'), { statusCode: 409 });
    const wrapped = asyncHandler(jest.fn().mockRejectedValueOnce(err));

    await wrapped(req, res);

    expect(res.status).toHaveBeenCalledWith(409);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      message: 'último recurso'
    });
  });
});
