// Mock the unified logging module so we can verify logError is called
// without writing to the actual error.log file
jest.mock('../src/utils/logging', () => ({
  logError: jest.fn().mockResolvedValue(),
  safeErrorMessage: jest.fn((err) => {
    if (!err) return 'An internal error occurred';
    return err.message || String(err);
  }),
  createLogger: jest.fn(() => ({
    info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn()
  })),
  LOG_LEVELS: { debug: 0, info: 1, warn: 2, error: 3 }
}));

const { errorMiddleware, notFoundHandler } = require('../src/utilities/error-handler');
const {
  AppError,
  ValidationError,
  AuthenticationError,
  NotFoundError,
  RateLimitError,
  DockerError,
} = require('../src/utilities/errors');

describe('Error Handler', () => {
  let req, res, next;

  beforeEach(() => {
    req = {
      method: 'GET',
      path: '/api/test',
      ip: '127.0.0.1',
      user: { id: 'user1' },
      body: {},
    };
    res = {
      status: jest.fn().mockReturnThis(),
      json: jest.fn().mockReturnThis(),
    };
    next = jest.fn();
  });

  describe('errorMiddleware', () => {
    it('returns 400 for ValidationError', () => {
      const err = new ValidationError('bad input', 'email');
      errorMiddleware(err, req, res, next);

      expect(res.status).toHaveBeenCalledWith(400);
      expect(res.json).toHaveBeenCalledWith(
        expect.objectContaining({
          success: false,
          error: 'bad input',
          code: 'DC-400',
          field: 'email',
        })
      );
    });

    it('returns 401 for AuthenticationError with requiresTotp', () => {
      const err = new AuthenticationError('auth needed', true);
      errorMiddleware(err, req, res, next);

      expect(res.status).toHaveBeenCalledWith(401);
      expect(res.json).toHaveBeenCalledWith(
        expect.objectContaining({
          success: false,
          error: 'auth needed',
          requiresTotp: true,
        })
      );
    });

    it('returns 404 for NotFoundError with resource', () => {
      const err = new NotFoundError('Service');
      errorMiddleware(err, req, res, next);

      expect(res.status).toHaveBeenCalledWith(404);
      expect(res.json).toHaveBeenCalledWith(
        expect.objectContaining({
          error: 'Service not found',
          resource: 'Service',
        })
      );
    });

    it('returns 429 for RateLimitError with retryAfter', () => {
      const err = new RateLimitError(30);
      errorMiddleware(err, req, res, next);

      expect(res.status).toHaveBeenCalledWith(429);
      expect(res.json).toHaveBeenCalledWith(
        expect.objectContaining({
          error: 'Rate limit exceeded',
          retryAfter: 30,
        })
      );
    });

    it('returns 500 with "Internal server error" for generic Error', () => {
      const err = new Error('db connection lost');
      errorMiddleware(err, req, res, next);

      expect(res.status).toHaveBeenCalledWith(500);
      expect(res.json).toHaveBeenCalledWith(
        expect.objectContaining({
          success: false,
          error: 'Internal server error', // NOT the real message
        })
      );
    });

    it('includes error code in DC-XXX format', () => {
      const err = new AppError('test', 418, 'DC-TEAPOT');
      errorMiddleware(err, req, res, next);

      expect(res.json).toHaveBeenCalledWith(
        expect.objectContaining({ code: 'DC-TEAPOT' })
      );
    });

    it('includes details for DockerError', () => {
      const err = new DockerError('container fail', 'create', { id: '123' });
      errorMiddleware(err, req, res, next);

      expect(res.json).toHaveBeenCalledWith(
        expect.objectContaining({
          details: { id: '123' },
        })
      );
    });

    it('includes stack trace in development mode', () => {
      const origEnv = process.env.NODE_ENV;
      process.env.NODE_ENV = 'development';

      const err = new AppError('test');
      errorMiddleware(err, req, res, next);

      const response = res.json.mock.calls[0][0];
      expect(response.stack).toBeDefined();

      process.env.NODE_ENV = origEnv;
    });

    it('excludes stack trace in production mode', () => {
      const origEnv = process.env.NODE_ENV;
      process.env.NODE_ENV = 'production';

      const err = new AppError('test');
      errorMiddleware(err, req, res, next);

      const response = res.json.mock.calls[0][0];
      expect(response.stack).toBeUndefined();

      process.env.NODE_ENV = origEnv;
    });

    it('logs non-operational errors as FATAL', () => {
      const origError = console.error;
      console.error = jest.fn();

      const err = new Error('programming bug');
      errorMiddleware(err, req, res, next);

      expect(console.error).toHaveBeenCalledWith(
        'FATAL: Non-operational error detected',
        expect.any(Object)
      );

      console.error = origError;
    });
  });

  describe('notFoundHandler', () => {
    it('passes NotFoundError to next()', () => {
      notFoundHandler(req, res, next);
      expect(next).toHaveBeenCalledWith(expect.any(NotFoundError));
      const passedError = next.mock.calls[0][0];
      expect(passedError.message).toContain('GET');
      expect(passedError.message).toContain('/api/test');
    });
  });
});
