/**
 * Tests for schema validation middleware
 */

import { describe, it, expect, beforeEach } from '@jest/globals';
import { z } from 'zod';
import { validateBody, validateQuery, validateParams } from '../middleware/validate';

describe('Schema Validation', () => {
  let mockRequest: any;
  let mockReply: any;
  let sentResponse: any;

  beforeEach(() => {
    mockRequest = {
      body: {},
      query: {},
      params: {},
    };
    mockReply = {
      status: jest.fn().mockReturnThis(),
      send: jest.fn((data) => {
        sentResponse = data;
      }),
    };
    sentResponse = null;
  });

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

    it('should accept valid body', async () => {
      mockRequest.body = {
        name: 'John Doe',
        age: 30,
        email: 'john@example.com',
      };

      const middleware = validateBody(schema);
      await middleware(mockRequest, mockReply);

      expect(mockReply.status).not.toHaveBeenCalled();
      expect(mockRequest.body).toEqual({
        name: 'John Doe',
        age: 30,
        email: 'john@example.com',
      });
    });

    it('should reject missing required field', async () => {
      mockRequest.body = {
        age: 30,
      };

      const middleware = validateBody(schema);
      await middleware(mockRequest, mockReply);

      expect(mockReply.status).toHaveBeenCalledWith(400);
      expect(sentResponse).toMatchObject({
        error: 'invalid_request',
        message: 'Request body validation failed',
      });
      expect(sentResponse.details.fieldErrors.name).toBeDefined();
      expect(sentResponse.details.fieldErrors.email).toBeDefined();
    });

    it('should reject invalid field type', async () => {
      mockRequest.body = {
        name: 'John Doe',
        age: 'thirty', // Should be number
        email: 'john@example.com',
      };

      const middleware = validateBody(schema);
      await middleware(mockRequest, mockReply);

      expect(mockReply.status).toHaveBeenCalledWith(400);
      expect(sentResponse.details.fieldErrors.age).toBeDefined();
    });

    it('should reject invalid email format', async () => {
      mockRequest.body = {
        name: 'John Doe',
        email: 'not-an-email',
      };

      const middleware = validateBody(schema);
      await middleware(mockRequest, mockReply);

      expect(mockReply.status).toHaveBeenCalledWith(400);
      expect(sentResponse.details.fieldErrors.email).toBeDefined();
    });

    it('should strip unknown fields', async () => {
      mockRequest.body = {
        name: 'John Doe',
        email: 'john@example.com',
        unknown: 'field',
      };

      const middleware = validateBody(schema);
      await middleware(mockRequest, mockReply);

      expect(mockReply.status).not.toHaveBeenCalled();
      expect(mockRequest.body).not.toHaveProperty('unknown');
    });
  });

  describe('Query Validation', () => {
    const schema = z.object({
      limit: z
        .string()
        .optional()
        .transform((v) => (v ? parseInt(v, 10) : 10))
        .refine((n) => !Number.isNaN(n) && n > 0 && n <= 100),
      status: z.enum(['active', 'inactive']).optional(),
    });

    it('should accept valid query', async () => {
      mockRequest.query = {
        limit: '20',
        status: 'active',
      };

      const middleware = validateQuery(schema);
      await middleware(mockRequest, mockReply);

      expect(mockReply.status).not.toHaveBeenCalled();
      expect(mockRequest.query.limit).toBe(20);
      expect(mockRequest.query.status).toBe('active');
    });

    it('should transform string to number', async () => {
      mockRequest.query = {
        limit: '50',
      };

      const middleware = validateQuery(schema);
      await middleware(mockRequest, mockReply);

      expect(mockReply.status).not.toHaveBeenCalled();
      expect(mockRequest.query.limit).toBe(50);
    });

    it('should reject invalid enum value', async () => {
      mockRequest.query = {
        status: 'pending', // Not in enum
      };

      const middleware = validateQuery(schema);
      await middleware(mockRequest, mockReply);

      expect(mockReply.status).toHaveBeenCalledWith(400);
      expect(sentResponse).toMatchObject({
        error: 'invalid_query',
        message: 'Query parameter validation failed',
      });
    });

    it('should reject limit out of range', async () => {
      mockRequest.query = {
        limit: '200', // > 100
      };

      const middleware = validateQuery(schema);
      await middleware(mockRequest, mockReply);

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

  describe('Params Validation', () => {
    const schema = z.object({
      id: z.string().uuid(),
    });

    it('should accept valid UUID', async () => {
      mockRequest.params = {
        id: '123e4567-e89b-12d3-a456-426614174000',
      };

      const middleware = validateParams(schema);
      await middleware(mockRequest, mockReply);

      expect(mockReply.status).not.toHaveBeenCalled();
    });

    it('should reject invalid UUID format', async () => {
      mockRequest.params = {
        id: 'not-a-uuid',
      };

      const middleware = validateParams(schema);
      await middleware(mockRequest, mockReply);

      expect(mockReply.status).toHaveBeenCalledWith(400);
      expect(sentResponse).toMatchObject({
        error: 'invalid_params',
        message: 'Route parameter validation failed',
      });
    });
  });
});

describe('Run Schemas', () => {
  describe('Create Run Schema', () => {
    it('should validate valid run creation', async () => {
      const { createRunSchema } = await import('../schemas/runs');
      
      const validData = {
        name: 'Test Run',
        input: { foo: 'bar' },
        clientToken: 'token-123',
        tags: ['test', 'example'],
      };

      const result = createRunSchema.safeParse(validData);
      expect(result.success).toBe(true);
    });

    it('should reject run without name', async () => {
      const { createRunSchema } = await import('../schemas/runs');
      
      const invalidData = {
        input: { foo: 'bar' },
      };

      const result = createRunSchema.safeParse(invalidData);
      expect(result.success).toBe(false);
    });

    it('should reject run with too many tags', async () => {
      const { createRunSchema } = await import('../schemas/runs');
      
      const invalidData = {
        name: 'Test',
        tags: Array(25).fill('tag'), // Max is 20
      };

      const result = createRunSchema.safeParse(invalidData);
      expect(result.success).toBe(false);
    });
  });

  describe('List Runs Query Schema', () => {
    it('should validate valid query', async () => {
      const { listRunsQuerySchema } = await import('../schemas/runs');
      
      const validQuery = {
        status: 'completed',
        limit: '50',
        offset: '10',
      };

      const result = listRunsQuerySchema.safeParse(validQuery);
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.limit).toBe(50);
        expect(result.data.offset).toBe(10);
      }
    });

    it('should reject invalid status', async () => {
      const { listRunsQuerySchema } = await import('../schemas/runs');
      
      const invalidQuery = {
        status: 'invalid-status',
      };

      const result = listRunsQuerySchema.safeParse(invalidQuery);
      expect(result.success).toBe(false);
    });

    it('should reject limit out of range', async () => {
      const { listRunsQuerySchema } = await import('../schemas/runs');
      
      const invalidQuery = {
        limit: '2000', // Max is 1000
      };

      const result = listRunsQuerySchema.safeParse(invalidQuery);
      expect(result.success).toBe(false);
    });
  });
});

