/**
 * Stationery routes — duplicate name prevention on create.
 *
 * The CSV import POSTs one row at a time to this route, and the route had no
 * duplicate check at all, so importing a file containing an item that already
 * existed simply created a second copy (TC_603). Manual creation and any direct
 * API call had the same hole. These pin the check, its case-insensitivity, and
 * that it is scoped to the company.
 */

import request from 'supertest';
import express from 'express';

const mockSave = jest.fn().mockResolvedValue(undefined);
const mockStationeryCtor: any = jest.fn().mockImplementation((data: any) => ({
  _id: 'stationery-new',
  ...data,
  save: mockSave,
}));
mockStationeryCtor.findOne = jest.fn();

jest.mock('../../models', () => ({
  getModels: () => ({ Stationery: mockStationeryCtor }),
}));

jest.mock('../../middleware/dualAuth', () => ({
  authenticateJwtOrApiToken: (req: any, _res: any, next: any) => {
    req.user = { _id: 'user-1', companyIds: ['company-1'], role: 'member' };
    next();
  },
}));
jest.mock('../../middleware/permissions', () => ({
  requirePermission: () => (_req: any, _res: any, next: any) => next(),
}));

import stationeryRouter, { STATIONERY_DUPLICATE_NAME_ERROR } from '../stationery';

const validBody = { name: 'Letterhead_v1', companyId: 'company-1', type: 'letterhead' };

/** The findOne call that carried the name condition. */
const nameQuery = () =>
  mockStationeryCtor.findOne.mock.calls.map((c: any[]) => c[0]).find((q: any) => q?.name);

describe('POST /api/stationery — duplicate name', () => {
  let app: express.Application;

  beforeEach(() => {
    app = express();
    app.use(express.json());
    app.use('/api/stationery', stationeryRouter);
    jest.clearAllMocks();
    mockSave.mockResolvedValue(undefined);
  });

  it('creates the item when the name is unused', async () => {
    mockStationeryCtor.findOne.mockResolvedValue(null);

    const res = await request(app).post('/api/stationery').send(validBody).expect(201);

    expect(res.body.name).toBe('Letterhead_v1');
    expect(mockSave).toHaveBeenCalledTimes(1);
  });

  it('rejects a duplicate with 409 and does not save', async () => {
    mockStationeryCtor.findOne.mockResolvedValue({ _id: 'existing', name: 'Letterhead_v1' });

    const res = await request(app).post('/api/stationery').send(validBody).expect(409);

    expect(res.body.error).toBe(STATIONERY_DUPLICATE_NAME_ERROR);
    expect(mockSave).not.toHaveBeenCalled();
  });

  it('matches case-insensitively and anchors the pattern', async () => {
    mockStationeryCtor.findOne.mockResolvedValue({ _id: 'existing' });

    await request(app).post('/api/stationery').send({ ...validBody, name: 'LETTERHEAD_V1' }).expect(409);

    expect(nameQuery()).toEqual({
      companyId: 'company-1',
      name: { $regex: '^LETTERHEAD_V1$', $options: 'i' },
    });
  });

  it('scopes the lookup to the company so other companies are unaffected', async () => {
    mockStationeryCtor.findOne.mockResolvedValue(null);

    await request(app).post('/api/stationery').send(validBody).expect(201);

    expect(nameQuery()?.companyId).toBe('company-1');
  });

  it('trims before comparing, so " Letterhead_v1 " is the same item', async () => {
    mockStationeryCtor.findOne.mockResolvedValue({ _id: 'existing' });

    await request(app).post('/api/stationery').send({ ...validBody, name: '  Letterhead_v1  ' }).expect(409);

    expect(nameQuery()?.name.$regex).toBe('^Letterhead_v1$');
  });

  it('escapes regex metacharacters in the name', async () => {
    mockStationeryCtor.findOne.mockResolvedValue(null);

    await request(app).post('/api/stationery').send({ ...validBody, name: 'Letter+head (v1).pdf' }).expect(201);

    // Unescaped, '+' and '(' would change the meaning of the pattern or throw.
    expect(nameQuery()?.name.$regex).toBe('^Letter\\+head \\(v1\\)\\.pdf$');
  });

  it('reports a unique-index violation as the same duplicate message', async () => {
    mockStationeryCtor.findOne.mockResolvedValue(null);
    const dupKeyError: any = new Error('E11000 duplicate key error');
    dupKeyError.code = 11000;
    mockSave.mockRejectedValue(dupKeyError);

    const res = await request(app).post('/api/stationery').send(validBody).expect(409);

    expect(res.body.error).toBe(STATIONERY_DUPLICATE_NAME_ERROR);
  });

  it('still rejects a missing name with the existing 400 validation', async () => {
    mockStationeryCtor.findOne.mockResolvedValue(null);

    await request(app).post('/api/stationery').send({ ...validBody, name: '' }).expect(400);

    expect(mockSave).not.toHaveBeenCalled();
  });
});
