// src/utils/apiResponse.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { Response } from 'express';
import {
  sendSuccess,
  sendNoContent,
  calculatePagination,
  sendPaginated,
  sendError,
  sendMessage,
  ErrorCode,
} from './apiResponse';

// Create a mock Express response
function createMockResponse(): Response {
  const res = {
    status: vi.fn().mockReturnThis(),
    json: vi.fn().mockReturnThis(),
    send: vi.fn().mockReturnThis(),
  } as unknown as Response;
  return res;
}

describe('apiResponse utilities', () => {
  let mockRes: Response;

  beforeEach(() => {
    mockRes = createMockResponse();
  });

  describe('sendSuccess', () => {
    it('should send success response with data and default status 200', () => {
      const data = { id: 1, name: 'Test' };

      sendSuccess(mockRes, data);

      expect(mockRes.status).toHaveBeenCalledWith(200);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data,
      });
    });

    it('should send success response with custom status code', () => {
      const data = { id: 1 };

      sendSuccess(mockRes, data, 201);

      expect(mockRes.status).toHaveBeenCalledWith(201);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data,
      });
    });

    it('should include meta when provided', () => {
      const data = { id: 1 };
      const meta = { requestId: 'req-123', timestamp: '2024-01-15T12:00:00Z' };

      sendSuccess(mockRes, data, 200, meta);

      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data,
        meta,
      });
    });

    it('should handle null data', () => {
      sendSuccess(mockRes, null);

      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data: null,
      });
    });

    it('should handle array data', () => {
      const data = [{ id: 1 }, { id: 2 }];

      sendSuccess(mockRes, data);

      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data,
      });
    });

    it('should handle empty object data', () => {
      sendSuccess(mockRes, {});

      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data: {},
      });
    });
  });

  describe('sendNoContent', () => {
    it('should send 204 status with no body', () => {
      sendNoContent(mockRes);

      expect(mockRes.status).toHaveBeenCalledWith(204);
      expect(mockRes.send).toHaveBeenCalledWith();
    });
  });

  describe('calculatePagination', () => {
    it('should calculate pagination for first page', () => {
      const result = calculatePagination({ page: 1, limit: 10, total: 100 });

      expect(result).toEqual({
        page: 1,
        limit: 10,
        total: 100,
        totalPages: 10,
        hasNextPage: true,
        hasPrevPage: false,
      });
    });

    it('should calculate pagination for middle page', () => {
      const result = calculatePagination({ page: 5, limit: 10, total: 100 });

      expect(result).toEqual({
        page: 5,
        limit: 10,
        total: 100,
        totalPages: 10,
        hasNextPage: true,
        hasPrevPage: true,
      });
    });

    it('should calculate pagination for last page', () => {
      const result = calculatePagination({ page: 10, limit: 10, total: 100 });

      expect(result).toEqual({
        page: 10,
        limit: 10,
        total: 100,
        totalPages: 10,
        hasNextPage: false,
        hasPrevPage: true,
      });
    });

    it('should handle single page result', () => {
      const result = calculatePagination({ page: 1, limit: 10, total: 5 });

      expect(result).toEqual({
        page: 1,
        limit: 10,
        total: 5,
        totalPages: 1,
        hasNextPage: false,
        hasPrevPage: false,
      });
    });

    it('should handle empty results', () => {
      const result = calculatePagination({ page: 1, limit: 10, total: 0 });

      expect(result).toEqual({
        page: 1,
        limit: 10,
        total: 0,
        totalPages: 0,
        hasNextPage: false,
        hasPrevPage: false,
      });
    });

    it('should handle non-even page boundaries', () => {
      const result = calculatePagination({ page: 1, limit: 10, total: 25 });

      expect(result).toEqual({
        page: 1,
        limit: 10,
        total: 25,
        totalPages: 3, // ceil(25/10) = 3
        hasNextPage: true,
        hasPrevPage: false,
      });
    });

    it('should handle page 2 of 3 with non-even total', () => {
      const result = calculatePagination({ page: 2, limit: 10, total: 25 });

      expect(result).toEqual({
        page: 2,
        limit: 10,
        total: 25,
        totalPages: 3,
        hasNextPage: true,
        hasPrevPage: true,
      });
    });

    it('should handle last page with non-even total', () => {
      const result = calculatePagination({ page: 3, limit: 10, total: 25 });

      expect(result).toEqual({
        page: 3,
        limit: 10,
        total: 25,
        totalPages: 3,
        hasNextPage: false,
        hasPrevPage: true,
      });
    });

    it('should handle limit of 1', () => {
      const result = calculatePagination({ page: 5, limit: 1, total: 10 });

      expect(result).toEqual({
        page: 5,
        limit: 1,
        total: 10,
        totalPages: 10,
        hasNextPage: true,
        hasPrevPage: true,
      });
    });

    it('should handle large limit with small total', () => {
      const result = calculatePagination({ page: 1, limit: 100, total: 5 });

      expect(result).toEqual({
        page: 1,
        limit: 100,
        total: 5,
        totalPages: 1,
        hasNextPage: false,
        hasPrevPage: false,
      });
    });
  });

  describe('sendPaginated', () => {
    it('should send paginated response with data and pagination meta', () => {
      const data = [{ id: 1 }, { id: 2 }];
      const pagination = { page: 1, limit: 10, total: 100 };

      sendPaginated(mockRes, data, pagination);

      expect(mockRes.status).toHaveBeenCalledWith(200);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data,
        meta: {
          pagination: {
            page: 1,
            limit: 10,
            total: 100,
            totalPages: 10,
            hasNextPage: true,
            hasPrevPage: false,
          },
        },
      });
    });

    it('should include additional meta when provided', () => {
      const data = [{ id: 1 }];
      const pagination = { page: 1, limit: 10, total: 1 };
      const meta = { requestId: 'req-456' };

      sendPaginated(mockRes, data, pagination, meta);

      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data,
        meta: {
          requestId: 'req-456',
          pagination: {
            page: 1,
            limit: 10,
            total: 1,
            totalPages: 1,
            hasNextPage: false,
            hasPrevPage: false,
          },
        },
      });
    });

    it('should handle empty array data', () => {
      const data: unknown[] = [];
      const pagination = { page: 1, limit: 10, total: 0 };

      sendPaginated(mockRes, data, pagination);

      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data: [],
        meta: {
          pagination: {
            page: 1,
            limit: 10,
            total: 0,
            totalPages: 0,
            hasNextPage: false,
            hasPrevPage: false,
          },
        },
      });
    });

    it('should always return status 200', () => {
      const data = [{ id: 1 }];
      const pagination = { page: 1, limit: 10, total: 1 };

      sendPaginated(mockRes, data, pagination);

      expect(mockRes.status).toHaveBeenCalledWith(200);
    });
  });

  describe('sendError', () => {
    it('should send error response with code and message', () => {
      sendError(mockRes, ErrorCode.VALIDATION_ERROR, 'Invalid input');

      expect(mockRes.status).toHaveBeenCalledWith(400);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: {
          code: ErrorCode.VALIDATION_ERROR,
          message: 'Invalid input',
        },
      });
    });

    it('should send error with custom status code', () => {
      sendError(mockRes, ErrorCode.NOT_FOUND, 'Resource not found', 404);

      expect(mockRes.status).toHaveBeenCalledWith(404);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: {
          code: ErrorCode.NOT_FOUND,
          message: 'Resource not found',
        },
      });
    });

    it('should include details when provided', () => {
      const details = [
        { field: 'email', message: 'Invalid email format' },
        { field: 'password', message: 'Password too short' },
      ];

      sendError(mockRes, ErrorCode.VALIDATION_ERROR, 'Validation failed', 400, details);

      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: {
          code: ErrorCode.VALIDATION_ERROR,
          message: 'Validation failed',
          details,
        },
      });
    });

    it('should include meta when provided', () => {
      const meta = { requestId: 'req-789', timestamp: '2024-01-15T12:00:00Z' };

      sendError(mockRes, ErrorCode.INTERNAL_ERROR, 'Server error', 500, undefined, meta);

      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: {
          code: ErrorCode.INTERNAL_ERROR,
          message: 'Server error',
        },
        meta,
      });
    });

    it('should include both details and meta when provided', () => {
      const details = { originalError: 'Database connection failed' };
      const meta = { requestId: 'req-000' };

      sendError(mockRes, ErrorCode.INTERNAL_ERROR, 'Database error', 500, details, meta);

      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: {
          code: ErrorCode.INTERNAL_ERROR,
          message: 'Database error',
          details,
        },
        meta,
      });
    });

    it('should accept string error codes', () => {
      sendError(mockRes, 'CUSTOM_ERROR', 'Custom error message', 400);

      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: {
          code: 'CUSTOM_ERROR',
          message: 'Custom error message',
        },
      });
    });

    it('should use default status 400 when not specified', () => {
      sendError(mockRes, ErrorCode.VALIDATION_ERROR, 'Error');

      expect(mockRes.status).toHaveBeenCalledWith(400);
    });

    it('should handle null details (not undefined)', () => {
      // null should be included as details, unlike undefined
      sendError(mockRes, ErrorCode.VALIDATION_ERROR, 'Error', 400, null);

      expect(mockRes.json).toHaveBeenCalledWith({
        success: false,
        error: {
          code: ErrorCode.VALIDATION_ERROR,
          message: 'Error',
          details: null,
        },
      });
    });
  });

  describe('sendMessage', () => {
    it('should send success response with message', () => {
      sendMessage(mockRes, 'Operation completed successfully');

      expect(mockRes.status).toHaveBeenCalledWith(200);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data: { message: 'Operation completed successfully' },
      });
    });

    it('should send message with custom status code', () => {
      sendMessage(mockRes, 'Resource created', 201);

      expect(mockRes.status).toHaveBeenCalledWith(201);
      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data: { message: 'Resource created' },
      });
    });

    it('should handle empty message', () => {
      sendMessage(mockRes, '');

      expect(mockRes.json).toHaveBeenCalledWith({
        success: true,
        data: { message: '' },
      });
    });
  });

  describe('ErrorCode re-export', () => {
    it('should export ErrorCode enum', () => {
      expect(ErrorCode).toBeDefined();
      expect(ErrorCode.VALIDATION_ERROR).toBeDefined();
      expect(ErrorCode.NOT_FOUND).toBeDefined();
      expect(ErrorCode.INTERNAL_ERROR).toBeDefined();
    });
  });
});
