/**
 * test/controllers/DuplicatedMenuController.test.js
 *
 * Tests de integración chica: el controller envuelto con asyncHandler
 * delega a `res.domainError(err)` cuando el service lanza, en lugar de
 * caer al 500 del antiguo `instanceof`.
 *
 * Mockeamos:
 *   - DuplicatedMenuService (global) → para controlar si resuelve o rechaza.
 *   - res.ok / res.domainError → para verificar a quién llama.
 *
 * No mockeamos asyncHandler ni la response domainError — el controller
 * debe usar la implementación real (eso es lo que se está probando).
 */

'use strict';

const path = require('path');

const CONTROLLER_PATH = path.resolve(
  __dirname, '..', '..', 'api', 'controllers', 'DuplicatedMenuController.js'
);

describe('controllers/DuplicatedMenuController.duplicate', () => {
  let controller;
  let mockDuplicateMenu;
  let res;

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

    mockDuplicateMenu = jest.fn();
    global.DuplicatedMenuService = {
      duplicateMenu: mockDuplicateMenu
    };
    global.sails = { log: { error: jest.fn(), info: jest.fn(), warn: jest.fn() } };

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

    controller = require(CONTROLLER_PATH);
  });

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

  function makeReq(body = {}) {
    return { body };
  }

  // -----------------------------------------------------------------------
  // Happy path
  // -----------------------------------------------------------------------

  it('responde 200 con el resultado del service', async () => {
    mockDuplicateMenu.mockResolvedValueOnce({ success: true, dryRun: false, foo: 'bar' });

    await controller.duplicate(
      makeReq({ email: { duplicateFrom: 'a@b.com', duplicateTo: 'c@d.com' } }),
      res
    );

    expect(mockDuplicateMenu).toHaveBeenCalledWith({
      duplicateFrom: 'a@b.com',
      duplicateTo:   'c@d.com',
      dryRun:        false
    });
    expect(res.ok).toHaveBeenCalledWith({ success: true, dryRun: false, foo: 'bar' });
    expect(res.domainError).not.toHaveBeenCalled();
  });

  // -----------------------------------------------------------------------
  // Manejo de errores de dominio
  // -----------------------------------------------------------------------

  it('cuando el service lanza con statusCode 404 → delega a res.domainError', async () => {
    const err = Object.assign(new Error('No se encuentra el restaurante principal'), {
      name: 'DuplicatedMenuError',
      statusCode: 404
    });
    mockDuplicateMenu.mockRejectedValueOnce(err);

    await controller.duplicate(
      makeReq({ email: { duplicateFrom: 'a@b.com', duplicateTo: 'c@d.com' } }),
      res
    );

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

  it('cuando el service lanza con statusCode 409 → delega a res.domainError', async () => {
    const err = Object.assign(new Error('Destino con menús'), { statusCode: 409 });
    mockDuplicateMenu.mockRejectedValueOnce(err);

    await controller.duplicate(
      makeReq({ email: { duplicateFrom: 'a@b.com', duplicateTo: 'c@d.com' } }),
      res
    );

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

  it('cuando el service lanza error inesperado (sin statusCode) también delega a domainError', async () => {
    const err = new Error('DB connection lost');
    mockDuplicateMenu.mockRejectedValueOnce(err);

    await controller.duplicate(
      makeReq({ email: { duplicateFrom: 'a@b.com', duplicateTo: 'c@d.com' } }),
      res
    );

    // domainError se encarga de mapear esto a 500 internamente.
    expect(res.domainError).toHaveBeenCalledWith(err);
  });

  // -----------------------------------------------------------------------
  // Coerción de dryRun
  // -----------------------------------------------------------------------

  it('dryRun true (boolean) llega como true al service', async () => {
    mockDuplicateMenu.mockResolvedValueOnce({});

    await controller.duplicate(
      makeReq({
        email: { duplicateFrom: 'a@b.com', duplicateTo: 'c@d.com' },
        dryRun: true
      }),
      res
    );

    expect(mockDuplicateMenu).toHaveBeenCalledWith(
      expect.objectContaining({ dryRun: true })
    );
  });

  it('dryRun ausente → false', async () => {
    mockDuplicateMenu.mockResolvedValueOnce({});

    await controller.duplicate(
      makeReq({ email: { duplicateFrom: 'a@b.com', duplicateTo: 'c@d.com' } }),
      res
    );

    expect(mockDuplicateMenu).toHaveBeenCalledWith(
      expect.objectContaining({ dryRun: false })
    );
  });

  it('dryRun string "true" se considera false (chequeo === true es estricto)', async () => {
    // Este caso cubre la salvaguarda: el validator ya hace toBoolean(), pero
    // el controller también es estricto por si la policy se desactiva.
    mockDuplicateMenu.mockResolvedValueOnce({});

    await controller.duplicate(
      makeReq({
        email: { duplicateFrom: 'a@b.com', duplicateTo: 'c@d.com' },
        dryRun: 'true'
      }),
      res
    );

    expect(mockDuplicateMenu).toHaveBeenCalledWith(
      expect.objectContaining({ dryRun: false })
    );
  });

  // -----------------------------------------------------------------------
  // Integración con domainError real (smoke test del pipeline completo)
  // -----------------------------------------------------------------------

  it('integración: error 404 del service termina con res.status(404).json({...})', async () => {
    // Simulamos `res.domainError` cargando el response real, así verificamos
    // que la cadena asyncHandler → res.domainError → status/json funciona.
    const domainError = require(
      path.resolve(__dirname, '..', '..', 'api', 'responses', 'domainError.js')
    );
    res.domainError = function (err) { return domainError.call({ res, req: {} }, err); };

    const err = Object.assign(
      new Error('No se encuentra el restaurante principal se cancela la clonación'),
      { statusCode: 404, name: 'DuplicatedMenuError' }
    );
    mockDuplicateMenu.mockRejectedValueOnce(err);

    await controller.duplicate(
      makeReq({ email: { duplicateFrom: 'a@b.com', duplicateTo: 'c@d.com' } }),
      res
    );

    expect(res.status).toHaveBeenCalledWith(404);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      message: 'No se encuentra el restaurante principal se cancela la clonación'
    });
  });
});

// =========================================================================
// GET /duplicatedMenu/compare
// =========================================================================

describe('controllers/DuplicatedMenuController.compare', () => {
  let controller;
  let mockCompareMenus;
  let res;

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

    mockCompareMenus = jest.fn();
    global.DuplicatedMenuService = {
      compareMenus: mockCompareMenus
    };
    global.sails = { log: { error: jest.fn(), info: jest.fn(), warn: jest.fn() } };

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

    controller = require(
      path.resolve(__dirname, '..', '..', 'api', 'controllers', 'DuplicatedMenuController.js')
    );
  });

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

  function makeReq(query = {}) {
    return { query };
  }

  it('200 OK con el resultado del service', async () => {
    const fakeResult = {
      success: true,
      restaurantOrigin: { menus: [], items: [], extraSections: [], extraItems: [] },
      restaurantDest:   { menus: [], items: [], extraSections: [], extraItems: [] },
      resultComparation: { overall: { similarity: 100 } }
    };
    mockCompareMenus.mockResolvedValueOnce(fakeResult);

    await controller.compare(
      makeReq({ duplicateFrom: 'a@b.com', duplicateTo: 'c@d.com' }),
      res
    );

    expect(mockCompareMenus).toHaveBeenCalledWith({
      duplicateFrom: 'a@b.com',
      duplicateTo:   'c@d.com'
    });
    expect(res.ok).toHaveBeenCalledWith(fakeResult);
    expect(res.domainError).not.toHaveBeenCalled();
  });

  it('error 404 del service → delega a res.domainError', async () => {
    const err = Object.assign(new Error('No existe origen'), { statusCode: 404 });
    mockCompareMenus.mockRejectedValueOnce(err);

    await controller.compare(
      makeReq({ duplicateFrom: 'a@b.com', duplicateTo: 'c@d.com' }),
      res
    );

    expect(res.domainError).toHaveBeenCalledWith(err);
    expect(res.ok).not.toHaveBeenCalled();
  });

  it('lee duplicateFrom/duplicateTo de req.query (no de req.body)', async () => {
    mockCompareMenus.mockResolvedValueOnce({});

    // Solo query, body intencionalmente vacío para confirmar que no se mezcla.
    await controller.compare(
      { query: { duplicateFrom: 'q@b.com', duplicateTo: 'q2@d.com' }, body: {} },
      res
    );

    expect(mockCompareMenus).toHaveBeenCalledWith({
      duplicateFrom: 'q@b.com',
      duplicateTo:   'q2@d.com'
    });
  });

  it('integración: error 404 termina con res.status(404).json({...})', async () => {
    const domainError = require(
      path.resolve(__dirname, '..', '..', 'api', 'responses', 'domainError.js')
    );
    res.domainError = function (err) { return domainError.call({ res, req: {} }, err); };

    const err = Object.assign(new Error('No existe el origen'), { statusCode: 404 });
    mockCompareMenus.mockRejectedValueOnce(err);

    await controller.compare(
      makeReq({ duplicateFrom: 'a@b.com', duplicateTo: 'c@d.com' }),
      res
    );

    expect(res.status).toHaveBeenCalledWith(404);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      message: 'No existe el origen'
    });
  });
});
