import { Request, Response } from 'express';
import { z } from 'zod';
import { validateDto } from '../validation.middleware';

// EN: Mock express types
// VI: Mock express types
const mockNext = jest.fn();
const mockJson = jest.fn();
const mockStatus = jest.fn().mockReturnValue({ json: mockJson });

// EN: Helper to create mock request/response
// VI: Helper để tạo mock request/response
const createMockReq = (overrides: any = {}): Partial<Request> => ({
  body: {},
  query: {},
  params: {},
  ...overrides,
});

const createMockRes = (): Partial<Response> => ({
  status: mockStatus,
  json: mockJson,
});

describe('Validation Middleware', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('validateDto', () => {
    const testSchema = z.object({
      name: z.string().min(1),
      age: z.number().min(0),
      email: z.string().email().optional(),
    });

    it('should pass validation for valid data', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const mockReq = createMockReq({
        body: { name: 'John', age: 25, email: 'john@example.com' },
      });
      const mockRes = createMockRes();

      // EN: Act
      // VI: Thực hiện
      const middleware = validateDto(testSchema);
      middleware(mockReq as Request, mockRes as Response, mockNext);

      // EN: Assert
      // VI: Kiểm tra
      expect(mockNext).toHaveBeenCalled();
      expect(mockReq.body).toEqual({
        name: 'John',
        age: 25,
        email: 'john@example.com',
      });
      expect(mockStatus).not.toHaveBeenCalled();
    });

    it('should sanitize string inputs by trimming whitespace', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const mockReq = createMockReq({
        body: { name: '  John  ', age: 25 },
      });
      const mockRes = createMockRes();

      // EN: Act
      // VI: Thực hiện
      const middleware = validateDto(testSchema);
      middleware(mockReq as Request, mockRes as Response, mockNext);

      // EN: Assert
      // VI: Kiểm tra
      expect(mockReq.body!.name).toBe('John'); // Trimmed
    });

    it('should return 400 for invalid data', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const mockReq = createMockReq({
        body: { name: '', age: -5 }, // Invalid: empty name, negative age
      });
      const mockRes = createMockRes();

      // EN: Act
      // VI: Thực hiện
      const middleware = validateDto(testSchema);
      middleware(mockReq as Request, mockRes as Response, mockNext);

      // EN: Assert
      // VI: Kiểm tra
      expect(mockNext).not.toHaveBeenCalled();
      expect(mockStatus).toHaveBeenCalledWith(400);
      expect(mockJson).toHaveBeenCalledWith({
        success: false,
        error: {
          code: 'VALIDATION_ERROR',
          message: 'Invalid request data / Dữ liệu request không hợp lệ',
          details: expect.any(Array),
        },
        timestamp: expect.any(String),
      });
    });

    it('should validate query parameters', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const querySchema = z.object({
        page: z.string().transform(Number),
        limit: z.string().transform(Number).optional(),
      });
      const mockReq = createMockReq({
        query: { page: '1', limit: '10' },
      });
      const mockRes = createMockRes();

      // EN: Act
      // VI: Thực hiện
      const middleware = validateDto(querySchema, 'query');
      middleware(mockReq as Request, mockRes as Response, mockNext);

      // EN: Assert
      // VI: Kiểm tra
      expect(mockNext).toHaveBeenCalled();
      expect(mockReq.query).toEqual({ page: 1, limit: 10 });
    });

    it('should validate route parameters', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const paramsSchema = z.object({
        id: z.string().uuid(),
      });
      const mockReq = createMockReq({
        params: { id: '123e4567-e89b-12d3-a456-426614174000' },
      });
      const mockRes = createMockRes();

      // EN: Act
      // VI: Thực hiện
      const middleware = validateDto(paramsSchema, 'params');
      middleware(mockReq as Request, mockRes as Response, mockNext);

      // EN: Assert
      // VI: Kiểm tra
      expect(mockNext).toHaveBeenCalled();
      expect(mockReq.params!.id).toBe('123e4567-e89b-12d3-a456-426614174000');
    });

    it('should handle nested object sanitization', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const nestedSchema = z.object({
        user: z.object({
          name: z.string(),
          settings: z.object({
            theme: z.string(),
          }),
        }),
      });
      const mockReq = createMockReq({
        body: {
          user: {
            name: '  Alice  ',
            settings: {
              theme: ' dark ',
            },
          },
        },
      });
      const mockRes = createMockRes();

      // EN: Act
      // VI: Thực hiện
      const middleware = validateDto(nestedSchema);
      middleware(mockReq as Request, mockRes as Response, mockNext);

      // EN: Assert
      // VI: Kiểm tra
      expect(mockReq.body!.user.name).toBe('Alice');
      expect(mockReq.body!.user.settings.theme).toBe('dark');
    });

    it('should handle array sanitization', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const arraySchema = z.object({
        tags: z.array(z.string()),
      });
      const mockReq = createMockReq({
        body: {
          tags: ['  react  ', '  typescript ', ' node '],
        },
      });
      const mockRes = createMockRes();

      // EN: Act
      // VI: Thực hiện
      const middleware = validateDto(arraySchema);
      middleware(mockReq as Request, mockRes as Response, mockNext);

      // EN: Assert
      // VI: Kiểm tra
      expect(mockReq.body!.tags).toEqual(['react', 'typescript', 'node']);
    });
  });
});