import { HttpException, HttpStatus } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { describe, expect, it, vi } from 'vitest';
import { DomainException } from '../../domain/domain-exception';
import { ErrorCode } from '../../domain/error-codes';
import { GlobalExceptionFilter } from '../filters/global-exception.filter';
import { type LoggerService } from '../logger.service';

function createMockHost(correlationId?: string) {
  const json = vi.fn();
  const status = vi.fn().mockReturnValue({ json });
  const request = {
    headers: { 'x-correlation-id': correlationId },
  };
  const response = { status };

  return {
    switchToHttp: () => ({
      getRequest: () => request,
      getResponse: () => response,
    }),
    json,
    status,
  };
}

describe('GlobalExceptionFilter', () => {
  const logger = {
    log: vi.fn(),
    error: vi.fn(),
    warn: vi.fn(),
    debug: vi.fn(),
    verbose: vi.fn(),
    child: vi.fn(),
  } as unknown as LoggerService;
  const filter = new GlobalExceptionFilter(logger);

  it('should handle DomainException with correct error code', () => {
    const exception = new DomainException(
      ErrorCode.USER_NOT_FOUND,
      'User not found',
      HttpStatus.NOT_FOUND,
    );
    const host = createMockHost('test-corr-id');

    filter.catch(exception, host as never);

    expect(host.status).toHaveBeenCalledWith(HttpStatus.NOT_FOUND);
    expect(host.json).toHaveBeenCalledWith(
      expect.objectContaining({
        statusCode: HttpStatus.NOT_FOUND,
        errorCode: ErrorCode.USER_NOT_FOUND,
        message: 'User not found',
        correlationId: 'test-corr-id',
      }),
    );
  });

  it('should handle standard HttpException', () => {
    const exception = new HttpException('Bad request', HttpStatus.BAD_REQUEST);
    const host = createMockHost();

    filter.catch(exception, host as never);

    expect(host.status).toHaveBeenCalledWith(HttpStatus.BAD_REQUEST);
    expect(host.json).toHaveBeenCalledWith(
      expect.objectContaining({
        statusCode: HttpStatus.BAD_REQUEST,
        errorCode: ErrorCode.BAD_REQUEST,
        message: 'Bad request',
      }),
    );
  });

  it('should handle unknown errors as 500', () => {
    const exception = new Error('Something broke');
    const host = createMockHost();

    filter.catch(exception, host as never);

    expect(host.status).toHaveBeenCalledWith(HttpStatus.INTERNAL_SERVER_ERROR);
    expect(host.json).toHaveBeenCalledWith(
      expect.objectContaining({
        statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
        errorCode: ErrorCode.INTERNAL_ERROR,
        message: 'Internal server error',
      }),
    );
  });

  it('should include timestamp in every response', () => {
    const exception = new Error('test');
    const host = createMockHost();

    filter.catch(exception, host as never);

    expect(host.json).toHaveBeenCalledWith(expect.objectContaining({ timestamp: expect.any(String) }));
  });

  describe('Prisma error handling', () => {
    it('should handle P2025 (record not found) as 404', () => {
      const exception = new Prisma.PrismaClientKnownRequestError(
        'An operation failed because it depends on one or more records that were required but not found.',
        { code: 'P2025', clientVersion: '5.0.0' },
      );
      const host = createMockHost();

      filter.catch(exception, host as never);

      expect(host.status).toHaveBeenCalledWith(HttpStatus.NOT_FOUND);
      expect(host.json).toHaveBeenCalledWith(
        expect.objectContaining({
          statusCode: HttpStatus.NOT_FOUND,
          errorCode: ErrorCode.NOT_FOUND,
          message: 'The requested resource was not found',
        }),
      );
    });

    it('should handle P2002 (unique constraint) as 409', () => {
      const exception = new Prisma.PrismaClientKnownRequestError(
        'Unique constraint failed on the fields: (`email`)',
        { code: 'P2002', clientVersion: '5.0.0' },
      );
      const host = createMockHost();

      filter.catch(exception, host as never);

      expect(host.status).toHaveBeenCalledWith(HttpStatus.CONFLICT);
      expect(host.json).toHaveBeenCalledWith(
        expect.objectContaining({
          statusCode: HttpStatus.CONFLICT,
          errorCode: ErrorCode.CONFLICT,
          message: 'A record with the given identifier already exists',
        }),
      );
    });

    it('should handle P2003 (foreign key constraint) as 400', () => {
      const exception = new Prisma.PrismaClientKnownRequestError(
        'Foreign key constraint failed on the field: `propertyId`',
        { code: 'P2003', clientVersion: '5.0.0' },
      );
      const host = createMockHost();

      filter.catch(exception, host as never);

      expect(host.status).toHaveBeenCalledWith(HttpStatus.BAD_REQUEST);
      expect(host.json).toHaveBeenCalledWith(
        expect.objectContaining({
          statusCode: HttpStatus.BAD_REQUEST,
          errorCode: ErrorCode.BAD_REQUEST,
          message: 'Referenced record does not exist',
        }),
      );
    });

    it('should handle unknown Prisma known errors as 500', () => {
      const exception = new Prisma.PrismaClientKnownRequestError(
        'Some other error',
        { code: 'P2999', clientVersion: '5.0.0' },
      );
      const host = createMockHost();

      filter.catch(exception, host as never);

      expect(host.status).toHaveBeenCalledWith(HttpStatus.INTERNAL_SERVER_ERROR);
      expect(host.json).toHaveBeenCalledWith(
        expect.objectContaining({
          statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
          errorCode: ErrorCode.INTERNAL_ERROR,
          message: 'Internal server error',
        }),
      );
    });

    it('should handle PrismaClientValidationError as 400', () => {
      const exception = new Prisma.PrismaClientValidationError(
        'Invalid query argument',
        { clientVersion: '5.0.0' },
      );
      const host = createMockHost();

      filter.catch(exception, host as never);

      expect(host.status).toHaveBeenCalledWith(HttpStatus.BAD_REQUEST);
      expect(host.json).toHaveBeenCalledWith(
        expect.objectContaining({
          statusCode: HttpStatus.BAD_REQUEST,
          errorCode: ErrorCode.BAD_REQUEST,
          message: 'Invalid request parameters',
        }),
      );
    });

    it('should not leak internal Prisma error details', () => {
      const exception = new Prisma.PrismaClientKnownRequestError(
        'Detailed internal error with table names and column info',
        { code: 'P2025', clientVersion: '5.0.0' },
      );
      const host = createMockHost();

      filter.catch(exception, host as never);

      const response = host.json.mock.calls[0][0];
      expect(response.message).not.toContain('table');
      expect(response.message).not.toContain('column');
    });
  });
});
