/**
 * test/lib/validatorPolicy.test.js
 *
 * Verifica el factory genérico que envuelve reglas de express-validator
 * en una policy de Sails.
 */

'use strict';

const path = require('path');
const { body, query } = require('express-validator');

const FACTORY_PATH = path.resolve(
  __dirname, '..', '..', 'api', 'lib', 'validatorPolicy.js'
);

describe('lib/validatorPolicy', () => {
  let validatorPolicy;
  let res;
  let next;

  beforeEach(() => {
    jest.resetModules();
    global.sails = { log: { error: jest.fn(), info: jest.fn(), warn: jest.fn() } };

    res = {
      status:      jest.fn().mockReturnThis(),
      json:        jest.fn().mockReturnThis(),
      domainError: undefined  // por default sin domainError, los tests específicos lo agregan
    };
    next = jest.fn();

    validatorPolicy = require(FACTORY_PATH);
  });

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

  it('throw si rules no es un array', () => {
    expect(() => validatorPolicy(null)).toThrow(TypeError);
    expect(() => validatorPolicy('foo')).toThrow(TypeError);
    expect(() => validatorPolicy({})).toThrow(TypeError);
  });

  it('body válido → llama next() y NO emite respuesta', async () => {
    const policy = validatorPolicy([
      body('email').isEmail()
    ]);

    const req = { body: { email: 'a@b.com' } };
    await policy(req, res, next);

    expect(next).toHaveBeenCalledTimes(1);
    expect(res.status).not.toHaveBeenCalled();
    expect(res.json).not.toHaveBeenCalled();
  });

  it('body inválido sin domainError → status 400 + shape estándar', async () => {
    const policy = validatorPolicy([
      body('email').isEmail().withMessage('email inválido')
    ]);

    const req = { body: { email: 'no-es-email' } };
    await policy(req, res, next);

    expect(next).not.toHaveBeenCalled();
    expect(res.status).toHaveBeenCalledWith(400);
    expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
      success: false,
      message: 'Error de validación en el body.',
      errors: expect.arrayContaining([
        expect.objectContaining({ field: 'email', message: 'email inválido' })
      ])
    }));
  });

  it('body inválido CON domainError → delega al custom response', async () => {
    res.domainError = jest.fn();

    const policy = validatorPolicy([
      body('email').isEmail().withMessage('email inválido')
    ]);

    const req = { body: { email: 'mal' } };
    await policy(req, res, next);

    expect(next).not.toHaveBeenCalled();
    expect(res.domainError).toHaveBeenCalledTimes(1);
    const errArg = res.domainError.mock.calls[0][0];
    expect(errArg.statusCode).toBe(400);
    expect(errArg.message).toBe('Error de validación en el body.');
    expect(errArg.errors).toEqual(expect.arrayContaining([
      expect.objectContaining({ field: 'email', message: 'email inválido' })
    ]));
  });

  it('opciones custom: statusCode + message + logPrefix', async () => {
    res.domainError = jest.fn();

    const policy = validatorPolicy(
      [ body('foo').exists().withMessage('foo requerido') ],
      { statusCode: 422, message: 'Body inválido para foo', logPrefix: '[fooPolicy]' }
    );

    const req = { body: {} };
    await policy(req, res, next);

    const err = res.domainError.mock.calls[0][0];
    expect(err.statusCode).toBe(422);
    expect(err.message).toBe('Body inválido para foo');
  });

  it('soporta reglas sobre query() (GET)', async () => {
    res.domainError = jest.fn();

    const policy = validatorPolicy([
      query('id').isInt({ min: 1 }).withMessage('id debe ser entero positivo')
    ]);

    const req = { body: {}, query: { id: 'abc' } };
    await policy(req, res, next);

    expect(res.domainError).toHaveBeenCalled();
    expect(next).not.toHaveBeenCalled();
  });

  it('multiple reglas, todos los errores se acumulan en errors[]', async () => {
    res.domainError = jest.fn();

    const policy = validatorPolicy([
      body('email').isEmail().withMessage('email inválido'),
      body('age').isInt({ min: 18 }).withMessage('age debe ser >= 18')
    ]);

    const req = { body: { email: 'mal', age: 5 } };
    await policy(req, res, next);

    const err = res.domainError.mock.calls[0][0];
    expect(err.errors).toHaveLength(2);
    expect(err.errors.map(e => e.field).sort()).toEqual(['age', 'email']);
  });

  it('si una regla lanza interno, responde 500 y loguea', async () => {
    res.domainError = jest.fn();

    // Regla "rota" que lanza al ejecutar.
    const brokenRule = {
      run: jest.fn().mockRejectedValue(new Error('boom interno'))
    };

    const policy = validatorPolicy([brokenRule], { logPrefix: '[testBroken]' });

    const req = { body: {} };
    await policy(req, res, next);

    expect(res.status).toHaveBeenCalledWith(500);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      message: 'Error interno al validar el body.'
    });
    expect(global.sails.log.error).toHaveBeenCalledWith(
      expect.stringContaining('[testBroken]'),
      expect.any(Error)
    );
  });
});
