/**
 * test/responses/domainError.test.js
 *
 * Verifica el custom response que centraliza el shape de respuesta para
 * errores de dominio. La detección es por duck-typing (err.statusCode),
 * no por instanceof — así evitamos el bug que provocaba 500 cuando la
 * referencia de la clase de error quedaba desincronizada.
 */

'use strict';

const path = require('path');

const RESPONSE_PATH = path.resolve(
  __dirname, '..', '..', 'api', 'responses', 'domainError.js'
);

describe('responses/domainError', () => {
  let domainError;
  let res;
  let logError;

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

    // Mock mínimo de res — captura status y json para poder hacer asserts.
    res = {
      status: jest.fn().mockReturnThis(),
      json:   jest.fn().mockReturnThis()
    };

    // Mock global de sails para no contaminar logs reales.
    logError = jest.fn();
    global.sails = { log: { error: logError, info: jest.fn(), warn: jest.fn() } };

    domainError = require(RESPONSE_PATH);
  });

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

  /** Helper para invocar `res.domainError(err)` con el `this` correcto. */
  function callDomainError(err) {
    return domainError.call({ res, req: {} }, err);
  }

  it('error con statusCode 404 → status 404 + shape estándar', () => {
    const err = Object.assign(new Error('No encontrado'), {
      statusCode: 404,
      name: 'DuplicatedMenuError'
    });

    callDomainError(err);

    expect(res.status).toHaveBeenCalledWith(404);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      message: 'No encontrado'
    });
    expect(logError).not.toHaveBeenCalled();
  });

  it('error con statusCode 409 → status 409', () => {
    const err = Object.assign(new Error('Conflicto destino con datos'), { statusCode: 409 });

    callDomainError(err);

    expect(res.status).toHaveBeenCalledWith(409);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      message: 'Conflicto destino con datos'
    });
  });

  it('error con statusCode 400 + errors[] (validación) los incluye', () => {
    const err = Object.assign(new Error('Validation failed'), {
      statusCode: 400,
      errors: [
        { field: 'email', message: 'invalid' },
        { field: 'pass',  message: 'required' }
      ]
    });

    callDomainError(err);

    expect(res.status).toHaveBeenCalledWith(400);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      message: 'Validation failed',
      errors: err.errors
    });
  });

  it('error con statusCode 422 + code (string) lo incluye en el body', () => {
    const err = Object.assign(new Error('Ups'), { statusCode: 422, code: 'INVALID_INPUT' });

    callDomainError(err);

    expect(res.status).toHaveBeenCalledWith(422);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      message: 'Ups',
      code: 'INVALID_INPUT'
    });
  });

  it('error sin statusCode → 500 con mensaje genérico (no filtra detalles) y log', () => {
    const err = new Error('database exploded — secret leak');

    callDomainError(err);

    expect(res.status).toHaveBeenCalledWith(500);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      message: 'Error interno del servidor.'
    });
    expect(logError).toHaveBeenCalledTimes(1);
    expect(logError.mock.calls[0][1]).toBe(err);
  });

  it('error con statusCode no numérico (string "404") cae en 500', () => {
    // Defensivo: por si alguien setea statusCode mal tipado.
    const err = Object.assign(new Error('mal tipado'), { statusCode: '404' });

    callDomainError(err);

    expect(res.status).toHaveBeenCalledWith(500);
    expect(logError).toHaveBeenCalled();
  });

  it('null/undefined → 500 sin crashear', () => {
    callDomainError(null);

    expect(res.status).toHaveBeenCalledWith(500);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      message: 'Error interno del servidor.'
    });
  });

  it('AdapterError → 422 con detail del mensaje y log', () => {
    const err = Object.assign(new Error('column cannot be null'), { name: 'AdapterError' });

    callDomainError(err);

    expect(res.status).toHaveBeenCalledWith(422);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      message: 'Error en la base de datos.',
      detail: 'column cannot be null'
    });
    expect(logError).toHaveBeenCalledTimes(1);
  });

  it('UsageError → 422 con detail', () => {
    const err = Object.assign(new Error('missing required field'), { name: 'UsageError' });

    callDomainError(err);

    expect(res.status).toHaveBeenCalledWith(422);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      message: 'Error en la base de datos.',
      detail: 'missing required field'
    });
  });

  it('AdapterError con .code (E_UNIQUE) lo incluye en el body', () => {
    const err = Object.assign(new Error('Duplicate entry'), { name: 'AdapterError', code: 'E_UNIQUE' });

    callDomainError(err);

    expect(res.status).toHaveBeenCalledWith(422);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      message: 'Error en la base de datos.',
      detail: 'Duplicate entry',
      code: 'E_UNIQUE'
    });
  });

  it('error inesperado en entorno no-producción incluye detail en la respuesta', () => {
    global.sails.config = { environment: 'development' };
    const err = new Error('debug info');

    callDomainError(err);

    expect(res.status).toHaveBeenCalledWith(500);
    expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
      success: false,
      message: 'Error interno del servidor.',
      detail: 'debug info'
    }));
  });

  it('NO usa instanceof (acepta errores plain object con statusCode)', () => {
    // Esta es la prueba clave: aunque el "error" no sea una instancia de la
    // clase original, mientras tenga statusCode, el response responde bien.
    // Esto reproduce el escenario de los módulos recargados.
    const fakeError = { message: 'simulated', statusCode: 404 };

    callDomainError(fakeError);

    expect(res.status).toHaveBeenCalledWith(404);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      message: 'simulated'
    });
    expect(logError).not.toHaveBeenCalled();
  });
});
