import {
    APIError,
    JSONError,
    VersionMismatchError,
    ServerUnreachableError,
    TimeoutError,
    RequestEntityTooLargeError,
    UnsupportedMediaTypeError,
    MaintenanceError,
    ThemeValidationError,
    HostLimitError,
    EmailError,
    ValidationError,
    AlreadyExistsError,
    errorsWithMessage,
    getErrorMessage,
    ErrorResponse
} from '../../../src/utils/errors';

describe('errors utils', () => {
    const mockErrorResponse: ErrorResponse = {
        errors: [{
            code: 'TEST_ERROR',
            context: 'Test context',
            details: 'Test details',
            ghostErrorCode: 'GHOST_TEST_ERROR',
            help: 'Test help',
            id: 'test-id',
            message: 'Test error message',
            property: 'testProperty',
            type: 'TestError'
        }]
    };

    describe('APIError', () => {
        it('creates error with custom message', () => {
            const error = new APIError(undefined, undefined, 'Custom error message');
            expect(error.message).toBe('Custom error message');
            expect(error.response).toBeUndefined();
            expect(error.data).toBeUndefined();
        });

        it('creates error with default message when no message provided', () => {
            const error = new APIError();
            expect(error.message).toBe('Something went wrong, please try again.');
        });

        it('creates error with response and data', () => {
            const mockResponse = new Response();
            const mockData = {error: 'test'};
            const error = new APIError(mockResponse, mockData);
            expect(error.response).toBe(mockResponse);
            expect(error.data).toBe(mockData);
        });

        it('generates message from admin API URL', () => {
            const mockResponse = new Response(null, {
                status: 400,
                statusText: 'Bad Request'
            });
            Object.defineProperty(mockResponse, 'url', {
                value: 'https://example.com/ghost/api/admin/posts/1234',
                writable: false
            });
            const error = new APIError(mockResponse);
            expect(error.message).toBe('Something went wrong while loading posts, please try again.');
        });

        it('handles admin API URL with underscores', () => {
            const mockResponse = new Response(null, {
                status: 400,
                statusText: 'Bad Request'
            });
            Object.defineProperty(mockResponse, 'url', {
                value: 'https://example.com/ghost/api/admin/email_previews/1234',
                writable: false
            });
            const error = new APIError(mockResponse);
            expect(error.message).toBe('Something went wrong while loading email previews, please try again.');
        });

        it('handles admin API URL with query params', () => {
            const mockResponse = new Response(null, {
                status: 400,
                statusText: 'Bad Request'
            });
            Object.defineProperty(mockResponse, 'url', {
                value: 'https://example.com/ghost/api/admin/members?filter=status:free',
                writable: false
            });
            const error = new APIError(mockResponse);
            expect(error.message).toBe('Something went wrong while loading members, please try again.');
        });

        it('uses custom message even with admin API URL', () => {
            const mockResponse = new Response(null, {
                status: 400,
                statusText: 'Bad Request'
            });
            Object.defineProperty(mockResponse, 'url', {
                value: 'https://example.com/ghost/api/admin/posts',
                writable: false
            });
            const error = new APIError(mockResponse, undefined, 'Custom message');
            expect(error.message).toBe('Custom message');
        });

        it('handles non-admin API URLs', () => {
            const mockResponse = new Response(null, {
                status: 400,
                statusText: 'Bad Request'
            });
            Object.defineProperty(mockResponse, 'url', {
                value: 'https://example.com/api/content/posts',
                writable: false
            });
            const error = new APIError(mockResponse);
            expect(error.message).toBe('Something went wrong, please try again.');
        });

        it('accepts error options', () => {
            const cause = new Error('Original error');
            const error = new APIError(undefined, undefined, 'Test error', {cause});
            expect(error.cause).toBe(cause);
        });
    });

    describe('JSONError', () => {
        it('creates error with response and error data', () => {
            const mockResponse = new Response();
            const error = new JSONError(mockResponse, mockErrorResponse, 'JSON error');
            expect(error.response).toBe(mockResponse);
            expect(error.data).toBe(mockErrorResponse);
            expect(error.message).toBe('JSON error');
        });

        it('inherits from APIError', () => {
            const mockResponse = new Response();
            const error = new JSONError(mockResponse, mockErrorResponse);
            expect(error).toBeInstanceOf(APIError);
        });
    });

    describe('VersionMismatchError', () => {
        it('creates error with specific message', () => {
            const mockResponse = new Response();
            const error = new VersionMismatchError(mockResponse, mockErrorResponse);
            expect(error.message).toBe('API server is running a newer version of Ghost, please upgrade.');
            expect(error.response).toBe(mockResponse);
            expect(error.data).toBe(mockErrorResponse);
        });

        it('inherits from JSONError', () => {
            const mockResponse = new Response();
            const error = new VersionMismatchError(mockResponse, mockErrorResponse);
            expect(error).toBeInstanceOf(JSONError);
        });
    });

    describe('ServerUnreachableError', () => {
        it('creates error with default message', () => {
            const error = new ServerUnreachableError();
            expect(error.message).toBe('Something went wrong, please try again.');
            expect(error.response).toBeUndefined();
            expect(error.data).toBeUndefined();
        });

        it('accepts error options', () => {
            const cause = new Error('Network error');
            const error = new ServerUnreachableError({cause});
            expect(error.cause).toBe(cause);
        });
    });

    describe('TimeoutError', () => {
        it('creates error with timeout message', () => {
            const error = new TimeoutError();
            expect(error.message).toBe('Request timed out, please try again.');
            expect(error.response).toBeUndefined();
            expect(error.data).toBeUndefined();
        });
    });

    describe('RequestEntityTooLargeError', () => {
        it('creates error with size limit message', () => {
            const mockResponse = new Response();
            const mockData = {size: 10485760};
            const error = new RequestEntityTooLargeError(mockResponse, mockData);
            expect(error.message).toBe('Request is larger than the maximum file size the server allows');
            expect(error.response).toBe(mockResponse);
            expect(error.data).toBe(mockData);
        });
    });

    describe('UnsupportedMediaTypeError', () => {
        it('creates error with media type message', () => {
            const mockResponse = new Response();
            const mockData = {type: 'application/unknown'};
            const error = new UnsupportedMediaTypeError(mockResponse, mockData);
            expect(error.message).toBe('Request contains an unknown or unsupported file type.');
            expect(error.response).toBe(mockResponse);
            expect(error.data).toBe(mockData);
        });
    });

    describe('MaintenanceError', () => {
        it('creates error with maintenance message', () => {
            const mockResponse = new Response();
            const mockData = {maintenance: true};
            const error = new MaintenanceError(mockResponse, mockData);
            expect(error.message).toBe('Ghost is currently undergoing maintenance, please wait a moment then retry.');
            expect(error.response).toBe(mockResponse);
            expect(error.data).toBe(mockData);
        });
    });

    describe('ThemeValidationError', () => {
        it('creates error with theme validation message', () => {
            const mockResponse = new Response();
            const error = new ThemeValidationError(mockResponse, mockErrorResponse);
            expect(error.message).toBe('Theme is not compatible or contains errors.');
            expect(error.response).toBe(mockResponse);
            expect(error.data).toBe(mockErrorResponse);
        });

        it('is included in errorsWithMessage', () => {
            expect(errorsWithMessage).toContain(ThemeValidationError);
        });
    });

    describe('HostLimitError', () => {
        it('creates error with host limit message', () => {
            const mockResponse = new Response();
            const error = new HostLimitError(mockResponse, mockErrorResponse);
            expect(error.message).toBe('A hosting plan limit was reached or exceeded.');
            expect(error.response).toBe(mockResponse);
            expect(error.data).toBe(mockErrorResponse);
        });

        it('is included in errorsWithMessage', () => {
            expect(errorsWithMessage).toContain(HostLimitError);
        });
    });

    describe('EmailError', () => {
        it('creates error with email settings message', () => {
            const mockResponse = new Response();
            const error = new EmailError(mockResponse, mockErrorResponse);
            expect(error.message).toBe('Please verify your email settings');
            expect(error.response).toBe(mockResponse);
            expect(error.data).toBe(mockErrorResponse);
        });

        it('is included in errorsWithMessage', () => {
            expect(errorsWithMessage).toContain(EmailError);
        });
    });

    describe('ValidationError', () => {
        it('uses first error message from response', () => {
            const mockResponse = new Response();
            const error = new ValidationError(mockResponse, mockErrorResponse);
            expect(error.message).toBe('Test error message');
            expect(error.response).toBe(mockResponse);
            expect(error.data).toBe(mockErrorResponse);
        });

        it('handles multiple errors in response', () => {
            const mockResponse = new Response();
            const multiErrorResponse: ErrorResponse = {
                errors: [
                    {...mockErrorResponse.errors[0], message: 'First error'},
                    {...mockErrorResponse.errors[0], message: 'Second error'}
                ]
            };
            const error = new ValidationError(mockResponse, multiErrorResponse);
            expect(error.message).toBe('First error');
        });

        it('is included in errorsWithMessage', () => {
            expect(errorsWithMessage).toContain(ValidationError);
        });
    });

    describe('AlreadyExistsError', () => {
        it('creates error with custom message', () => {
            const error = new AlreadyExistsError('Item already exists');
            expect(error.message).toBe('Item already exists');
        });

        it('creates error without message', () => {
            const error = new AlreadyExistsError();
            expect(error.message).toBe('');
        });

        it('inherits from Error', () => {
            const error = new AlreadyExistsError();
            expect(error).toBeInstanceOf(Error);
        });
    });

    describe('errorsWithMessage', () => {
        it('contains all expected error types', () => {
            expect(errorsWithMessage).toHaveLength(4);
            expect(errorsWithMessage).toContain(ValidationError);
            expect(errorsWithMessage).toContain(ThemeValidationError);
            expect(errorsWithMessage).toContain(HostLimitError);
            expect(errorsWithMessage).toContain(EmailError);
        });
    });

    describe('getErrorMessage', () => {
        const makeValidationError = (context: string | null) => {
            const data: ErrorResponse = {
                errors: [{
                    code: 'VALIDATION',
                    context,
                    details: null,
                    ghostErrorCode: null,
                    help: 'Help text',
                    id: 'error-id',
                    message: 'Validation error, cannot edit label.',
                    property: null,
                    type: 'ValidationError'
                }]
            };
            return new ValidationError({} as Response, data);
        };

        it('returns the context of a ValidationError', () => {
            const error = makeValidationError('Label already exists');
            expect(getErrorMessage(error, 'Fallback message')).toBe('Label already exists');
        });

        it('falls back to the error message when context is empty', () => {
            const error = makeValidationError(null);
            expect(getErrorMessage(error, 'Fallback message')).toBe('Validation error, cannot edit label.');
        });

        it('returns the fallback for other errors', () => {
            expect(getErrorMessage(new Error('boom'), 'Fallback message')).toBe('Fallback message');
            expect(getErrorMessage(undefined, 'Fallback message')).toBe('Fallback message');
        });
    });

    describe('ErrorResponse interface', () => {
        it('validates error response structure', () => {
            const response: ErrorResponse = {
                errors: [{
                    code: 'ERR_CODE',
                    context: null,
                    details: null,
                    ghostErrorCode: null,
                    help: 'Help text',
                    id: 'error-id',
                    message: 'Error message',
                    property: null,
                    type: 'ErrorType'
                }]
            };

            expect(response.errors).toHaveLength(1);
            expect(response.errors[0].code).toBe('ERR_CODE');
            expect(response.errors[0].context).toBeNull();
        });
    });
});