/*
 * Copyright (C) 2026 Fluxer Contributors
 *
 * This file is part of Fluxer.
 *
 * Fluxer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Fluxer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
 */

import {
	clearTestEmails,
	createAuthHarness,
	createUniqueEmail,
	createUniqueUsername,
	listTestEmails,
	registerUser,
} from '@fluxer/api/src/auth/tests/AuthTestUtils';
import type {ApiTestHarness} from '@fluxer/api/src/test/ApiTestHarness';
import {HTTP_STATUS} from '@fluxer/api/src/test/TestConstants';
import {createBuilderWithoutAuth} from '@fluxer/api/src/test/TestRequestBuilder';
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';

const HTTP_TOO_MANY_REQUESTS = 429;

interface IpAuthorizationResponse {
	ticket?: string;
	ip_authorization_required?: boolean;
	email?: string;
	resend_available_in?: number;
	code?: string;
	message?: string;
}

interface ResendErrorResponse {
	code?: string;
	message?: string;
	resend_available_in?: number;
	retry_after?: number;
}

describe('Auth IP Authorization Resend', () => {
	let harness: ApiTestHarness;

	beforeAll(async () => {
		harness = await createAuthHarness();
	});

	beforeEach(async () => {
		await harness.reset();
		await clearTestEmails(harness);
	});

	afterAll(async () => {
		await harness?.shutdown();
	});

	async function triggerIpAuthorization(email: string, password: string, ip: string): Promise<IpAuthorizationResponse> {
		const ipAuthResp = await createBuilderWithoutAuth<IpAuthorizationResponse>(harness)
			.post('/auth/login')
			.body({email, password})
			.header('x-forwarded-for', ip)
			.expect(HTTP_STATUS.FORBIDDEN)
			.execute();
		expect(ipAuthResp.ip_authorization_required).toBe(true);
		expect(ipAuthResp.ticket).toBeTruthy();
		return ipAuthResp;
	}

	describe('Resend rate limit enforcement', () => {
		it('returns 429 when resend is attempted immediately after ticket creation', async () => {
			const email = createUniqueEmail('ip-resend-immediate');
			const password = 'a-strong-password';

			await registerUser(harness, {
				email,
				username: createUniqueUsername('resendimm'),
				global_name: 'Resend Immediate User',
				password,
				date_of_birth: '2000-01-01',
				consent: true,
			});

			const newIP = '10.200.210.220';
			const ipAuthResp = await triggerIpAuthorization(email, password, newIP);

			const errorResp = await createBuilderWithoutAuth<ResendErrorResponse>(harness)
				.post('/auth/ip-authorization/resend')
				.body({ticket: ipAuthResp.ticket})
				.header('x-forwarded-for', newIP)
				.expect(HTTP_TOO_MANY_REQUESTS)
				.execute();
			expect(errorResp.code).toBe(APIErrorCodes.IP_AUTHORIZATION_RESEND_COOLDOWN);
		});

		it('returns resend_available_in when rate limited', async () => {
			const email = createUniqueEmail('ip-resend-cooldown');
			const password = 'a-strong-password';

			await registerUser(harness, {
				email,
				username: createUniqueUsername('resendcool'),
				global_name: 'Resend Cooldown User',
				password,
				date_of_birth: '2000-01-01',
				consent: true,
			});

			const newIP = '10.230.240.250';
			const ipAuthResp = await triggerIpAuthorization(email, password, newIP);

			const errorResp = await createBuilderWithoutAuth<ResendErrorResponse>(harness)
				.post('/auth/ip-authorization/resend')
				.body({ticket: ipAuthResp.ticket})
				.header('x-forwarded-for', newIP)
				.expect(HTTP_TOO_MANY_REQUESTS)
				.execute();
			expect(errorResp.code).toBe(APIErrorCodes.IP_AUTHORIZATION_RESEND_COOLDOWN);
			expect(typeof errorResp.resend_available_in).toBe('number');
			expect(errorResp.resend_available_in).toBeGreaterThan(0);
		});
	});

	describe('Multiple resend attempts handling', () => {
		it('returns 429 for multiple consecutive resend attempts', async () => {
			const email = createUniqueEmail('ip-multi-resend');
			const password = 'a-strong-password';

			await registerUser(harness, {
				email,
				username: createUniqueUsername('multiresend'),
				global_name: 'Multi Resend User',
				password,
				date_of_birth: '2000-01-01',
				consent: true,
			});

			const newIP = '10.30.31.32';
			const ipAuthResp = await triggerIpAuthorization(email, password, newIP);

			for (let i = 0; i < 3; i++) {
				await createBuilderWithoutAuth(harness)
					.post('/auth/ip-authorization/resend')
					.body({ticket: ipAuthResp.ticket})
					.header('x-forwarded-for', newIP)
					.expect(HTTP_TOO_MANY_REQUESTS)
					.execute();
			}
		});
	});

	describe('Resend with already-used ticket', () => {
		it('returns 429 when resend flag is already set on the ticket', async () => {
			const email = createUniqueEmail('ip-resend-used');
			const password = 'a-strong-password';

			const reg = await registerUser(harness, {
				email,
				username: createUniqueUsername('resendused'),
				global_name: 'Resend Used User',
				password,
				date_of_birth: '2000-01-01',
				consent: true,
			});

			const ticket = `ticket-used-${Date.now()}`;
			const token = `token-used-${Date.now()}`;

			await createBuilderWithoutAuth(harness)
				.post('/test/auth/ip-authorization')
				.body({
					ticket,
					token,
					user_id: reg.user_id,
					email,
					username: 'resend-used-user',
					client_ip: '203.0.113.10',
					user_agent: 'IntegrationTest/1.0',
					client_location: 'Testland',
					resend_used: true,
					created_at: Date.now() - 2 * 60 * 1000,
					ttl_seconds: 900,
				})
				.execute();

			const errorResp = await createBuilderWithoutAuth<ResendErrorResponse>(harness)
				.post('/auth/ip-authorization/resend')
				.body({ticket})
				.expect(HTTP_TOO_MANY_REQUESTS)
				.execute();
			expect(errorResp.code).toBe(APIErrorCodes.IP_AUTHORIZATION_RESEND_LIMIT_EXCEEDED);
		});

		it('returns 429 after IP has already been authorized', async () => {
			const email = createUniqueEmail('ip-resend-after-auth');
			const password = 'a-strong-password';

			await registerUser(harness, {
				email,
				username: createUniqueUsername('resendafterauth'),
				global_name: 'Resend After Auth User',
				password,
				date_of_birth: '2000-01-01',
				consent: true,
			});

			await clearTestEmails(harness);

			const newIP = '10.26.27.28';
			const ipAuthResp = await triggerIpAuthorization(email, password, newIP);

			const emails = await listTestEmails(harness);
			const ipAuthEmail = emails.find((e) => e.type === 'ip_authorization' && e.to === email);
			expect(ipAuthEmail).toBeDefined();

			const authToken = ipAuthEmail!.metadata['token'];
			expect(authToken).toBeTruthy();

			await createBuilderWithoutAuth(harness)
				.post('/auth/authorize-ip')
				.body({token: authToken})
				.header('x-forwarded-for', newIP)
				.expect(HTTP_STATUS.NO_CONTENT)
				.execute();

			await createBuilderWithoutAuth(harness)
				.post('/auth/ip-authorization/resend')
				.body({ticket: ipAuthResp.ticket})
				.header('x-forwarded-for', newIP)
				.expect(HTTP_STATUS.BAD_REQUEST)
				.execute();
		});
	});

	describe('Resend with empty ticket', () => {
		it('returns 400 when ticket is empty string', async () => {
			await createBuilderWithoutAuth(harness)
				.post('/auth/ip-authorization/resend')
				.body({ticket: ''})
				.expect(HTTP_STATUS.BAD_REQUEST)
				.execute();
		});
	});

	describe('Resend with invalid ticket', () => {
		it('returns 400 when ticket does not exist', async () => {
			const errorResp = await createBuilderWithoutAuth<ResendErrorResponse>(harness)
				.post('/auth/ip-authorization/resend')
				.body({ticket: 'invalid-ticket-xyz-12345'})
				.expect(HTTP_STATUS.BAD_REQUEST)
				.execute();
			expect(errorResp.code).toBeDefined();
			expect(errorResp.message).toBeDefined();
		});

		it('returns 400 when ticket has expired', async () => {
			const email = createUniqueEmail('ip-ticket-expired');
			const password = 'a-strong-password';

			await registerUser(harness, {
				email,
				username: createUniqueUsername('ticketexp'),
				global_name: 'Ticket Expired User',
				password,
				date_of_birth: '2000-01-01',
				consent: true,
			});

			const newIP = '10.70.80.90';
			const ipAuthResp = await triggerIpAuthorization(email, password, newIP);

			await createBuilderWithoutAuth(harness)
				.post('/test/auth/ip-authorization/expire')
				.body({ticket: ipAuthResp.ticket})
				.execute();

			await createBuilderWithoutAuth(harness)
				.post('/auth/ip-authorization/resend')
				.body({ticket: ipAuthResp.ticket})
				.header('x-forwarded-for', newIP)
				.expect(HTTP_STATUS.BAD_REQUEST)
				.execute();
		});
	});

	describe('Resend response format validation', () => {
		it('returns proper error structure with code and message on rate limit', async () => {
			const email = createUniqueEmail('ip-resend-format');
			const password = 'a-strong-password';

			await registerUser(harness, {
				email,
				username: createUniqueUsername('resendformat'),
				global_name: 'Resend Format User',
				password,
				date_of_birth: '2000-01-01',
				consent: true,
			});

			const newIP = '10.231.241.251';
			const ipAuthResp = await triggerIpAuthorization(email, password, newIP);

			const errorResp = await createBuilderWithoutAuth<ResendErrorResponse>(harness)
				.post('/auth/ip-authorization/resend')
				.body({ticket: ipAuthResp.ticket})
				.header('x-forwarded-for', newIP)
				.expect(HTTP_TOO_MANY_REQUESTS)
				.execute();

			expect(errorResp.code).toBe(APIErrorCodes.IP_AUTHORIZATION_RESEND_COOLDOWN);
			expect(typeof errorResp.message).toBe('string');
			expect(errorResp.message!.length).toBeGreaterThan(0);
		});

		it('returns proper error structure when ticket is invalid', async () => {
			const errorResp = await createBuilderWithoutAuth<ResendErrorResponse>(harness)
				.post('/auth/ip-authorization/resend')
				.body({ticket: 'nonexistent-ticket-abc'})
				.expect(HTTP_STATUS.BAD_REQUEST)
				.execute();

			expect(typeof errorResp.code).toBe('string');
			expect(typeof errorResp.message).toBe('string');
		});

		it('returns proper error structure when resend limit exceeded', async () => {
			const email = createUniqueEmail('ip-limit-format');
			const password = 'a-strong-password';

			const reg = await registerUser(harness, {
				email,
				username: createUniqueUsername('limitformat'),
				global_name: 'Limit Format User',
				password,
				date_of_birth: '2000-01-01',
				consent: true,
			});

			const ticket = `ticket-limit-${Date.now()}`;
			const token = `token-limit-${Date.now()}`;

			await createBuilderWithoutAuth(harness)
				.post('/test/auth/ip-authorization')
				.body({
					ticket,
					token,
					user_id: reg.user_id,
					email,
					username: 'limit-format-user',
					client_ip: '203.0.113.20',
					user_agent: 'IntegrationTest/1.0',
					client_location: 'Testland',
					resend_used: true,
					created_at: Date.now() - 2 * 60 * 1000,
					ttl_seconds: 900,
				})
				.execute();

			const errorResp = await createBuilderWithoutAuth<ResendErrorResponse>(harness)
				.post('/auth/ip-authorization/resend')
				.body({ticket})
				.expect(HTTP_TOO_MANY_REQUESTS)
				.execute();

			expect(errorResp.code).toBe(APIErrorCodes.IP_AUTHORIZATION_RESEND_LIMIT_EXCEEDED);
			expect(typeof errorResp.message).toBe('string');
		});
	});
});
