/*
 * 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 {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {HttpStatus} from '@fluxer/constants/src/HttpConstants';
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
import {InvalidPhoneNumberError} from '@fluxer/errors/src/domains/auth/InvalidPhoneNumberError';
import {UnknownChannelError} from '@fluxer/errors/src/domains/channel/UnknownChannelError';
import {UnknownMessageError} from '@fluxer/errors/src/domains/channel/UnknownMessageError';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
import {ForbiddenError} from '@fluxer/errors/src/domains/core/ForbiddenError';
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
import {InternalServerError} from '@fluxer/errors/src/domains/core/InternalServerError';
import {NotFoundError} from '@fluxer/errors/src/domains/core/NotFoundError';
import {FluxerError} from '@fluxer/errors/src/FluxerError';
import {describe, expect, it} from 'vitest';

interface ErrorResponse {
	code: string;
	message: string;
	[key: string]: unknown;
}

describe('Domain Errors', () => {
	describe('core domain errors', () => {
		describe('BadRequestError', () => {
			it('should create error with required code', () => {
				const error = new BadRequestError({code: APIErrorCodes.INVALID_REQUEST});

				expect(error.status).toBe(400);
				expect(error.code).toBe(APIErrorCodes.INVALID_REQUEST);
				expect(error.message).toBe(APIErrorCodes.INVALID_REQUEST);
			});

			it('should allow custom message', () => {
				const error = new BadRequestError({
					code: APIErrorCodes.INVALID_FORM_BODY,
					message: 'Custom bad request message',
				});

				expect(error.message).toBe('Custom bad request message');
			});

			it('should include data', () => {
				const error = new BadRequestError({
					code: APIErrorCodes.INVALID_REQUEST,
					data: {field: 'test'},
				});

				expect(error.data).toEqual({field: 'test'});
			});

			it('should include headers', () => {
				const error = new BadRequestError({
					code: APIErrorCodes.INVALID_REQUEST,
					headers: {'X-Custom': 'value'},
				});

				expect(error.headers).toEqual({'X-Custom': 'value'});
			});

			it('should include messageVariables for i18n', () => {
				const error = new BadRequestError({
					code: APIErrorCodes.INVALID_REQUEST,
					messageVariables: {count: 5},
				});

				expect(error.messageVariables).toEqual({count: 5});
			});

			it('should be instance of FluxerError', () => {
				const error = new BadRequestError({code: APIErrorCodes.INVALID_REQUEST});
				expect(error).toBeInstanceOf(FluxerError);
			});
		});

		describe('NotFoundError', () => {
			it('should create error with status 404', () => {
				const error = new NotFoundError({code: APIErrorCodes.UNKNOWN_USER});

				expect(error.status).toBe(404);
				expect(error.code).toBe(APIErrorCodes.UNKNOWN_USER);
				expect(error.message).toBe(APIErrorCodes.UNKNOWN_USER);
			});

			it('should allow messageVariables for i18n', () => {
				const error = new NotFoundError({
					code: APIErrorCodes.UNKNOWN_USER,
					messageVariables: {userId: '12345'},
				});

				expect(error.messageVariables).toEqual({userId: '12345'});
			});

			it('should be instance of FluxerError', () => {
				const error = new NotFoundError({code: APIErrorCodes.UNKNOWN_USER});
				expect(error).toBeInstanceOf(FluxerError);
			});
		});

		describe('ForbiddenError', () => {
			it('should create error with status 403', () => {
				const error = new ForbiddenError({code: APIErrorCodes.ACCESS_DENIED});

				expect(error.status).toBe(403);
				expect(error.code).toBe(APIErrorCodes.ACCESS_DENIED);
				expect(error.message).toBe(APIErrorCodes.ACCESS_DENIED);
			});

			it('should be instance of FluxerError', () => {
				const error = new ForbiddenError({code: APIErrorCodes.ACCESS_DENIED});
				expect(error).toBeInstanceOf(FluxerError);
			});
		});

		describe('InternalServerError', () => {
			it('should create error with status 500', () => {
				const error = new InternalServerError({code: APIErrorCodes.GENERAL_ERROR});

				expect(error.status).toBe(500);
				expect(error.code).toBe(APIErrorCodes.GENERAL_ERROR);
				expect(error.message).toBe(APIErrorCodes.GENERAL_ERROR);
			});

			it('should be instance of FluxerError', () => {
				const error = new InternalServerError({code: APIErrorCodes.GENERAL_ERROR});
				expect(error).toBeInstanceOf(FluxerError);
			});
		});

		describe('InputValidationError', () => {
			it('should create error with validation errors', () => {
				const error = new InputValidationError([{path: 'email', message: 'Invalid email format'}]);

				expect(error.status).toBe(400);
				expect(error.code).toBe(APIErrorCodes.INVALID_FORM_BODY);
				expect(error.data).toEqual({
					errors: [{path: 'email', message: 'Invalid email format'}],
				});
			});

			it('should support localized errors', () => {
				const error = new InputValidationError(
					[{path: 'name', message: ValidationErrorCodes.EMAIL_IS_REQUIRED}],
					[{path: 'name', code: ValidationErrorCodes.EMAIL_IS_REQUIRED}],
				);

				expect(error.localizedErrors).toEqual([{path: 'name', code: ValidationErrorCodes.EMAIL_IS_REQUIRED}]);
				expect(error.getLocalizedErrors()).toEqual([{path: 'name', code: ValidationErrorCodes.EMAIL_IS_REQUIRED}]);
			});

			it('should return null for localizedErrors when not provided', () => {
				const error = new InputValidationError([{path: 'field', message: 'error'}]);

				expect(error.localizedErrors).toBeNull();
				expect(error.getLocalizedErrors()).toBeNull();
			});

			it('should create from single field using static method', () => {
				const error = InputValidationError.create('username', 'Username is required');

				expect(error).toBeInstanceOf(InputValidationError);
				expect(error.data).toEqual({
					errors: [{path: 'username', message: 'Username is required'}],
				});
			});

			it('should create from multiple fields using static method', () => {
				const error = InputValidationError.createMultiple([
					{field: 'email', message: 'Invalid email'},
					{field: 'password', message: 'Password too short'},
				]);

				expect(error.data).toEqual({
					errors: [
						{path: 'email', message: 'Invalid email'},
						{path: 'password', message: 'Password too short'},
					],
				});
			});

			it('should create from error code using static method', () => {
				const error = InputValidationError.fromCode('email', ValidationErrorCodes.EMAIL_IS_REQUIRED, {maxLength: 255});

				expect(error.localizedErrors).toEqual([
					{path: 'email', code: ValidationErrorCodes.EMAIL_IS_REQUIRED, variables: {maxLength: 255}},
				]);
			});

			it('should create from multiple error codes using static method', () => {
				const error = InputValidationError.fromCodes([
					{path: 'email', code: ValidationErrorCodes.EMAIL_IS_REQUIRED},
					{path: 'name', code: ValidationErrorCodes.STRING_LENGTH_INVALID, variables: {max: 100}},
				]);

				expect(error.localizedErrors).toHaveLength(2);
			});
		});
	});

	describe('auth domain errors', () => {
		describe('InvalidPhoneNumberError', () => {
			it('should have correct code from APIErrorCodes', () => {
				const error = new InvalidPhoneNumberError();

				expect(error.code).toBe(APIErrorCodes.INVALID_PHONE_NUMBER);
				expect(error.status).toBe(HttpStatus.BAD_REQUEST);
			});

			it('should be instance of BadRequestError', () => {
				const error = new InvalidPhoneNumberError();
				expect(error).toBeInstanceOf(BadRequestError);
			});

			it('should be instance of FluxerError', () => {
				const error = new InvalidPhoneNumberError();
				expect(error).toBeInstanceOf(FluxerError);
			});
		});
	});

	describe('channel domain errors', () => {
		describe('UnknownChannelError', () => {
			it('should have correct code from APIErrorCodes', () => {
				const error = new UnknownChannelError();

				expect(error.code).toBe(APIErrorCodes.UNKNOWN_CHANNEL);
				expect(error.status).toBe(HttpStatus.NOT_FOUND);
			});

			it('should be instance of NotFoundError', () => {
				const error = new UnknownChannelError();
				expect(error).toBeInstanceOf(NotFoundError);
			});

			it('should be instance of FluxerError', () => {
				const error = new UnknownChannelError();
				expect(error).toBeInstanceOf(FluxerError);
			});
		});

		describe('UnknownMessageError', () => {
			it('should have correct code from APIErrorCodes', () => {
				const error = new UnknownMessageError();

				expect(error.code).toBe(APIErrorCodes.UNKNOWN_MESSAGE);
				expect(error.status).toBe(HttpStatus.NOT_FOUND);
			});

			it('should be instance of NotFoundError', () => {
				const error = new UnknownMessageError();
				expect(error).toBeInstanceOf(NotFoundError);
			});
		});
	});

	describe('error response generation', () => {
		it('should generate correct JSON response for domain errors', async () => {
			const error = new UnknownChannelError();
			const response = error.getResponse();

			expect(response.status).toBe(404);
			expect(response.headers.get('Content-Type')).toBe('application/json');

			const body = (await response.json()) as ErrorResponse;
			expect(body.code).toBe(APIErrorCodes.UNKNOWN_CHANNEL);
		});

		it('should include data in response', async () => {
			const error = new BadRequestError({
				code: APIErrorCodes.INVALID_REQUEST,
				data: {field: 'test', reason: 'invalid'},
			});

			const response = error.getResponse();
			const body = (await response.json()) as ErrorResponse;

			expect(body).toEqual({
				code: APIErrorCodes.INVALID_REQUEST,
				message: APIErrorCodes.INVALID_REQUEST,
				field: 'test',
				reason: 'invalid',
			});
		});

		it('should include custom headers in response', async () => {
			const error = new ForbiddenError({
				code: APIErrorCodes.ACCESS_DENIED,
				headers: {'X-Permission-Required': 'admin'},
			});

			const response = error.getResponse();

			expect(response.headers.get('X-Permission-Required')).toBe('admin');
		});
	});

	describe('error serialization', () => {
		it('should serialize domain errors to JSON correctly', () => {
			const error = new UnknownChannelError();
			const json = error.toJSON();

			expect(json).toEqual({
				code: APIErrorCodes.UNKNOWN_CHANNEL,
				message: APIErrorCodes.UNKNOWN_CHANNEL,
			});
		});

		it('should include data in JSON serialization', () => {
			const error = new BadRequestError({
				code: APIErrorCodes.INVALID_REQUEST,
				data: {extra: 'info'},
			});

			const json = error.toJSON();

			expect(json).toEqual({
				code: APIErrorCodes.INVALID_REQUEST,
				message: APIErrorCodes.INVALID_REQUEST,
				extra: 'info',
			});
		});
	});

	describe('error inheritance chain', () => {
		it('should maintain correct prototype chain', () => {
			const error = new InvalidPhoneNumberError();

			expect(error).toBeInstanceOf(InvalidPhoneNumberError);
			expect(error).toBeInstanceOf(BadRequestError);
			expect(error).toBeInstanceOf(FluxerError);
			expect(error).toBeInstanceOf(Error);
		});

		it('should be catchable at any level of the chain', () => {
			const error = new UnknownChannelError();

			try {
				throw error;
			} catch (e) {
				if (e instanceof NotFoundError) {
					expect(e.code).toBe(APIErrorCodes.UNKNOWN_CHANNEL);
				}
			}

			try {
				throw error;
			} catch (e) {
				if (e instanceof FluxerError) {
					expect(e.status).toBe(404);
				}
			}

			try {
				throw error;
			} catch (e) {
				if (e instanceof Error) {
					expect(e).toBeInstanceOf(UnknownChannelError);
				}
			}
		});
	});
});
