/**
 * H-06: authenticate middleware — async/await pattern
 * Ensures errors in session validation are caught properly (no unhandled rejection).
 */

import { NextFunction } from 'express';

// ---- mock dependencies ----
const mockVerifyAccessToken = jest.fn();
const mockValidateSession = jest.fn();

jest.mock('../../../src/services/auth', () => ({
	TokenService: { verifyAccessToken: mockVerifyAccessToken },
	SessionService: { validateSession: mockValidateSession },
}));
jest.mock('../../../src/middlewares/platform', () => ({
	detectPlatform: (_req: any, _res: any, next: any) => next(),
}));
jest.mock('../../../src/services/storage/redisService', () => ({
	__esModule: true,
	default: { getInstance: jest.fn(() => ({ get: jest.fn(), set: jest.fn(), delete: jest.fn() })) },
}));

import { authenticate } from '../../../src/middlewares/auth';

// Minimal request / response builders
const makeReq = (overrides: Partial<any> = {}): any => ({
	cookies: {},
	headers: {},
	header: (_name: string) => null,
	platform: undefined,
	user: undefined,
	...overrides,
});

const makeRes = (): any => {
	const res: any = {};
	res.error = jest.fn();
	return res;
};

describe('authenticate middleware (H-06: async)', () => {
	beforeEach(() => {
		mockVerifyAccessToken.mockReset();
		mockValidateSession.mockReset();
	});

	it('calls res.error(401) when no token provided', async () => {
		const req = makeReq();
		const res = makeRes();
		const next = jest.fn();
		await authenticate(req, res, next);
		expect(res.error).toHaveBeenCalledWith(expect.objectContaining({ httpStatus: 401 }));
		expect(next).not.toHaveBeenCalled();
	});

	it('calls next() when token is valid and session exists', async () => {
		const decoded = { id: 'user1', email: 'user@example.com' };
		mockVerifyAccessToken.mockReturnValue(decoded);
		mockValidateSession.mockResolvedValue({ id: 'user1' });
		const req = makeReq({ cookies: { access_token: 'valid-token' } });
		const res = makeRes();
		const next = jest.fn();
		await authenticate(req, res, next);
		expect(next).toHaveBeenCalled();
		expect(req.user).toEqual(decoded);
	});

	it('calls res.error(401) when session is null (invalid session)', async () => {
		mockVerifyAccessToken.mockReturnValue({ id: 'user1' });
		mockValidateSession.mockResolvedValue(null);
		const req = makeReq({ cookies: { access_token: 'valid-token' } });
		const res = makeRes();
		const next = jest.fn();
		await authenticate(req, res, next);
		expect(res.error).toHaveBeenCalledWith(expect.objectContaining({ httpStatus: 401 }));
		expect(next).not.toHaveBeenCalled();
	});

	it('catches session validation error and calls res.error(401) — no unhandled rejection', async () => {
		mockVerifyAccessToken.mockReturnValue({ id: 'user1' });
		mockValidateSession.mockRejectedValue(new Error('DB connection lost'));
		const req = makeReq({ cookies: { access_token: 'valid-token' } });
		const res = makeRes();
		const next = jest.fn();
		// Should NOT throw — error is caught inside the middleware
		await expect(authenticate(req, res, next)).resolves.toBeUndefined();
		expect(res.error).toHaveBeenCalledWith(expect.objectContaining({ httpStatus: 401 }));
		expect(next).not.toHaveBeenCalled();
	});

	it('accepts token from Authorization Bearer header', async () => {
		const decoded = { id: 'user2' };
		mockVerifyAccessToken.mockReturnValue(decoded);
		mockValidateSession.mockResolvedValue({ id: 'user2' });
		const req = makeReq({
			header: (name: string) => (name === 'Authorization' ? 'Bearer bearer-token' : null),
		});
		const res = makeRes();
		const next = jest.fn();
		await authenticate(req, res, next);
		expect(next).toHaveBeenCalled();
	});
});
