const errorHandler = require('../../../shared/middleware/errorHandler');
const {
    AppError,
    NotFoundError,
    ValidationError,
    ConflictError,
    UnauthorizedError,
    ForbiddenError,
} = require('../../../shared/errors');

describe('errorHandler middleware', () => {
    let req, res, next;

    beforeEach(() => {
        req = {};
        res = { status: jest.fn().mockReturnThis(), json: jest.fn() };
        next = jest.fn();
    });

    // --- AppError subclasses ---

    it('should handle NotFoundError with 404', () => {
        const err = new NotFoundError('Task not found');
        errorHandler(err, req, res, next);

        expect(res.status).toHaveBeenCalledWith(404);
        expect(res.json).toHaveBeenCalledWith({
            error: 'Task not found',
            code: 'NOT_FOUND',
        });
    });

    it('should handle ValidationError with 400', () => {
        const err = new ValidationError('Title is required');
        errorHandler(err, req, res, next);

        expect(res.status).toHaveBeenCalledWith(400);
        expect(res.json).toHaveBeenCalledWith({
            error: 'Title is required',
            code: 'VALIDATION_ERROR',
        });
    });

    it('should handle ConflictError with 409', () => {
        const err = new ConflictError();
        errorHandler(err, req, res, next);

        expect(res.status).toHaveBeenCalledWith(409);
        expect(res.json).toHaveBeenCalledWith({
            error: 'Resource already exists',
            code: 'CONFLICT',
        });
    });

    it('should handle UnauthorizedError with 401', () => {
        const err = new UnauthorizedError();
        errorHandler(err, req, res, next);

        expect(res.status).toHaveBeenCalledWith(401);
        expect(res.json).toHaveBeenCalledWith({
            error: 'Unauthorized',
            code: 'UNAUTHORIZED',
        });
    });

    it('should handle ForbiddenError with 403', () => {
        const err = new ForbiddenError();
        errorHandler(err, req, res, next);

        expect(res.status).toHaveBeenCalledWith(403);
        expect(res.json).toHaveBeenCalledWith({
            error: 'Forbidden',
            code: 'FORBIDDEN',
        });
    });

    it('should handle generic AppError with custom status', () => {
        const err = new AppError('Rate limited', 429, 'RATE_LIMITED');
        errorHandler(err, req, res, next);

        expect(res.status).toHaveBeenCalledWith(429);
        expect(res.json).toHaveBeenCalledWith({
            error: 'Rate limited',
            code: 'RATE_LIMITED',
        });
    });

    // --- Sequelize errors ---

    it('should handle SequelizeValidationError with 400', () => {
        const err = {
            name: 'SequelizeValidationError',
            errors: [
                { message: 'email must be unique' },
                { message: 'name cannot be null' },
            ],
        };
        errorHandler(err, req, res, next);

        expect(res.status).toHaveBeenCalledWith(400);
        expect(res.json).toHaveBeenCalledWith({
            error: 'email must be unique, name cannot be null',
            code: 'VALIDATION_ERROR',
        });
    });

    it('should handle SequelizeUniqueConstraintError with 409', () => {
        const err = { name: 'SequelizeUniqueConstraintError' };
        errorHandler(err, req, res, next);

        expect(res.status).toHaveBeenCalledWith(409);
        expect(res.json).toHaveBeenCalledWith({
            error: 'A resource with this identifier already exists.',
            code: 'CONFLICT',
        });
    });

    // --- Unknown errors ---

    it('should handle unknown errors with 500 in non-production', () => {
        const originalEnv = process.env.NODE_ENV;
        process.env.NODE_ENV = 'test';

        const err = new Error('Something unexpected');
        errorHandler(err, req, res, next);

        expect(res.status).toHaveBeenCalledWith(500);
        expect(res.json).toHaveBeenCalledWith({
            error: 'Something unexpected',
            code: 'INTERNAL_ERROR',
        });

        process.env.NODE_ENV = originalEnv;
    });

    it('should hide error message in production', () => {
        const originalEnv = process.env.NODE_ENV;
        process.env.NODE_ENV = 'production';

        const err = new Error('Database credentials leaked');
        errorHandler(err, req, res, next);

        expect(res.status).toHaveBeenCalledWith(500);
        expect(res.json).toHaveBeenCalledWith({
            error: 'Internal server error',
            code: 'INTERNAL_ERROR',
        });

        process.env.NODE_ENV = originalEnv;
    });

    it('should use statusCode from unknown error if present', () => {
        const err = new Error('Bad gateway');
        err.statusCode = 502;
        errorHandler(err, req, res, next);

        expect(res.status).toHaveBeenCalledWith(502);
    });

    it('should default to 500 when no statusCode on unknown error', () => {
        const err = new Error('Oops');
        errorHandler(err, req, res, next);

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