/**
 * Unit tests for auth/refresh handler — C-03
 *
 * After the fix, the `X-Include-Tokens` client header MUST NOT cause raw JWT
 * tokens to be exposed in the response body outside of development mode.
 */

// ── Mocks ────────────────────────────────────────────────────────────────────
const mockRefreshToken = jest.fn();
const mockGetAccessTokenCookieOptions = jest.fn().mockReturnValue({
	httpOnly: true,
	secure: false,
	sameSite: 'lax',
	maxAge: 900000,
	path: '/api',
});
const mockGetRefreshTokenCookieOptions = jest.fn().mockReturnValue({
	httpOnly: true,
	secure: false,
	sameSite: 'lax',
	maxAge: 604800000,
	path: '/api/v1/auth/refresh',
});
const mockParseTimeToSeconds = jest.fn().mockReturnValue(900);

jest.mock('#services/auth', () => ({
	SessionService: { refreshToken: (...args: any[]) => mockRefreshToken(...args) },
	TokenService: {
		getAccessTokenCookieOptions: () => mockGetAccessTokenCookieOptions(),
		getRefreshTokenCookieOptions: () => mockGetRefreshTokenCookieOptions(),
		parseTimeToSeconds: (...args: any[]) => mockParseTimeToSeconds(...args),
	},
}));
jest.mock('#middlewares/validators', () => ({
	validateZod: () => (_req: any, _res: any, next: any) => next(),
}));

// ── Helpers ──────────────────────────────────────────────────────────────────
import { createMockResponse } from '@helpers/request';

const TOKENS = { accessToken: 'jwt.access.token', refreshToken: 'jwt.refresh.token' };

function makeReq(overrides: Record<string, any> = {}) {
	return {
		body: { refresh_token: 'old-refresh-token' },
		cookies: {},
		headers: {},
		ip: '127.0.0.1',
		get: jest.fn().mockReturnValue('Mozilla/5.0'),
		...overrides,
	} as any;
}

function makeRes() {
	const res = createMockResponse() as any;
	res.cookie = jest.fn().mockReturnThis();
	res.json = jest.fn().mockReturnThis();
	res.error = jest.fn().mockReturnThis();
	return res;
}

import refreshModule from '#controllers/api/v1/auth/refresh';

function getHandler() {
	const resource = refreshModule({} as any);
	return (resource.post! as any).handler;
}

// ── Tests ────────────────────────────────────────────────────────────────────
describe('auth/refresh › X-Include-Tokens header bypass (C-03)', () => {
	beforeEach(() => {
		jest.clearAllMocks();
		mockRefreshToken.mockResolvedValue(TOKENS);
		process.env.NODE_ENV = 'production'; // Tokens should NOT be in response
	});

	afterEach(() => {
		delete process.env.NODE_ENV;
	});

	it('does NOT include raw tokens in response body in production mode', async () => {
		const req = makeReq({
			// Attacker sends X-Include-Tokens: true
			headers: { 'x-include-tokens': 'true' },
		});
		const res = makeRes();

		await getHandler()(req, res);

		const responseData = res.json.mock.calls[0][0]?.data;
		expect(responseData?.access_token).toBeUndefined();
		expect(responseData?.refresh_token).toBeUndefined();
	});

	it('still sets HttpOnly cookies even when header is sent', async () => {
		const req = makeReq({ headers: { 'x-include-tokens': 'true' } });
		const res = makeRes();

		await getHandler()(req, res);

		expect(res.cookie).toHaveBeenCalledWith('access_token', TOKENS.accessToken, expect.anything());
		expect(res.cookie).toHaveBeenCalledWith('refresh_token', TOKENS.refreshToken, expect.anything());
	});

	it('includes tokens in response body in development mode (intended)', async () => {
		process.env.NODE_ENV = 'development';

		const req = makeReq({ headers: {} });
		const res = makeRes();

		await getHandler()(req, res);

		const responseData = res.json.mock.calls[0][0]?.data;
		expect(responseData?.access_token).toBe(TOKENS.accessToken);
		expect(responseData?.refresh_token).toBe(TOKENS.refreshToken);
	});

	it('does NOT include tokens in body even in production with header=true', async () => {
		process.env.NODE_ENV = 'production';

		const req = makeReq({ headers: { 'x-include-tokens': 'true' } });
		const res = makeRes();

		await getHandler()(req, res);

		const responseData = res.json.mock.calls[0][0]?.data;
		// Must be undefined — header-based override is removed
		expect(responseData?.access_token).toBeUndefined();
	});

	it('omits tokens in body when NODE_ENV=staging (not dev)', async () => {
		process.env.NODE_ENV = 'staging';

		const req = makeReq({ headers: {} });
		const res = makeRes();

		await getHandler()(req, res);

		const responseData = res.json.mock.calls[0][0]?.data;
		expect(responseData?.access_token).toBeUndefined();
	});

	it('returns 500 on SessionService error', async () => {
		mockRefreshToken.mockRejectedValueOnce(new Error('Redis error'));

		const req = makeReq({ headers: {} });
		const res = makeRes();

		await getHandler()(req, res);

		expect(res.error).toHaveBeenCalledTimes(1);
		expect(res.json).not.toHaveBeenCalled();
	});

	it('includes session expiry info in all modes', async () => {
		const req = makeReq({ headers: {} });
		const res = makeRes();

		await getHandler()(req, res);

		const responseData = res.json.mock.calls[0][0]?.data;
		expect(responseData?.session?.expires_at).toBeInstanceOf(Date);
		expect(responseData?.session?.refresh_expires_at).toBeInstanceOf(Date);
	});
});
