import { describe, it, expect, beforeEach, vi } from 'vitest';
import { generateZodSchema, generateMockFromZodSchema } from './zod-mock-generator';
import type { Schema } from '../types';

describe('Zod Mock Generator', () => {
  describe('generateZodSchema', () => {
    it('generates z.string() for string type', () => {
      const schema: Schema = { type: 'string' };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.string()');
    });

    it('generates z.number() for number type', () => {
      const schema: Schema = { type: 'number' };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.number()');
    });

    it('generates z.number().int() for integer type', () => {
      const schema: Schema = { type: 'integer' };
      const result = generateZodSchema(schema);
      expect(result).toContain('z.number().int()');
    });

    it('generates z.boolean() for boolean type', () => {
      const schema: Schema = { type: 'boolean' };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.boolean()');
    });

    it('generates z.null() for null type', () => {
      const schema: Schema = { type: 'null' };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.null()');
    });

    it('handles string with uuid format', () => {
      const schema: Schema = { type: 'string', format: 'uuid' };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.string().uuid()');
    });

    it('handles string with email format', () => {
      const schema: Schema = { type: 'string', format: 'email' };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.string().email()');
    });

    it('handles string with url format', () => {
      const schema: Schema = { type: 'string', format: 'url' };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.string().url()');
    });

    it('handles string with date format', () => {
      const schema: Schema = { type: 'string', format: 'date' };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.string().date()');
    });

    it('handles string with date-time format', () => {
      const schema: Schema = { type: 'string', format: 'date-time' };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.string().datetime()');
    });

    it('handles string with phone format', () => {
      const schema: Schema = { type: 'string', format: 'phone' };
      const result = generateZodSchema(schema);
      expect(result).toContain('z.string().regex');
      expect(result).toContain('\\+?[1-9]\\d{1,14}');
    });

    it('handles string with minLength', () => {
      const schema: Schema = { type: 'string', minLength: 5 };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.string().min(5)');
    });

    it('handles string with maxLength', () => {
      const schema: Schema = { type: 'string', maxLength: 100 };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.string().max(100)');
    });

    it('handles string with pattern', () => {
      const schema: Schema = { type: 'string', pattern: '^[A-Z]+$' };
      const result = generateZodSchema(schema);
      expect(result).toContain('.regex(/^[A-Z]+$/)');
    });

    it('handles number with minimum', () => {
      const schema: Schema = { type: 'number', minimum: 0 };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.number().min(0)');
    });

    it('handles number with maximum', () => {
      const schema: Schema = { type: 'number', maximum: 100 };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.number().max(100)');
    });

    it('handles number with exclusive minimum', () => {
      const schema: Schema = { type: 'number', exclusiveMinimum: 0 };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.number().gt(0)');
    });

    it('handles number with exclusive maximum', () => {
      const schema: Schema = { type: 'number', exclusiveMaximum: 100 };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.number().lt(100)');
    });

    it('handles object with properties', () => {
      const schema: Schema = {
        type: 'object',
        properties: {
          name: { type: 'string' },
          age: { type: 'number' },
        },
        required: ['name'],
      };
      const result = generateZodSchema(schema);
      expect(result).toContain('z.object({');
      expect(result).toContain('name: z.string()');
      expect(result).toContain('age: z.number().optional()');
    });

    it('handles array type', () => {
      const schema: Schema = {
        type: 'array',
        items: { type: 'string' },
      };
      const result = generateZodSchema(schema);
      expect(result).toBe('z.array(z.string())');
    });

    it('handles array with minItems', () => {
      const schema: Schema = {
        type: 'array',
        items: { type: 'string' },
        minItems: 1,
      };
      const result = generateZodSchema(schema);
      expect(result).toContain('.min(1)');
    });

    it('handles array with maxItems', () => {
      const schema: Schema = {
        type: 'array',
        items: { type: 'string' },
        maxItems: 10,
      };
      const result = generateZodSchema(schema);
      expect(result).toContain('.max(10)');
    });
  });

  describe('generateMockFromZodSchema', () => {
    beforeEach(() => {
      vi.spyOn(Math, 'random').mockReturnValue(0.5);
    });

    describe('primitive types', () => {
      it('generates mock for z.string()', () => {
        const zodSchema = 'z.string()';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('string');
        expect(result).toBeTruthy();
      });

      it('generates mock for z.number()', () => {
        const zodSchema = 'z.number()';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('number');
      });

      it('generates mock for z.boolean()', () => {
        const zodSchema = 'z.boolean()';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('boolean');
      });

      it('generates null for z.null()', () => {
        const zodSchema = 'z.null()';
        const result = generateMockFromZodSchema(zodSchema);
        expect(result).toBe(null);
      });

      it('omits null when omitNulls option is true', () => {
        const zodSchema = 'z.null()';
        const result = generateMockFromZodSchema(zodSchema, {}, { omitNulls: true });
        expect(result).toBe(undefined);
      });
    });

    describe('string validations', () => {
      it('generates email for z.string().email()', () => {
        const zodSchema = 'z.string().email()';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('string');
        expect(result).toMatch(/^[\w.-]+@[\w.-]+\.\w+$/);
      });

      it('generates UUID for z.string().uuid()', () => {
        const zodSchema = 'z.string().uuid()';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('string');
        expect(result).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
      });

      it('generates URL for z.string().url()', () => {
        const zodSchema = 'z.string().url()';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('string');
        expect(result).toMatch(/^https?:\/\/.+/);
      });

      it('generates date for z.string().date()', () => {
        const zodSchema = 'z.string().date()';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('string');
        expect(result).toMatch(/^\d{4}-\d{2}-\d{2}$/);
      });

      it('generates datetime for z.string().datetime()', () => {
        const zodSchema = 'z.string().datetime()';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('string');
        expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
      });

      it('generates string with min length for z.string().min()', () => {
        const zodSchema = 'z.string().min(10)';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('string');
        expect((result as string).length).toBeGreaterThanOrEqual(10);
      });

      it('generates string with max length for z.string().max()', () => {
        const zodSchema = 'z.string().max(5)';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('string');
        expect((result as string).length).toBeLessThanOrEqual(5);
      });
    });

    describe('number validations', () => {
      it('generates number with min for z.number().min()', () => {
        const zodSchema = 'z.number().min(10)';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('number');
        expect(result as number).toBeGreaterThanOrEqual(10);
      });

      it('generates number with max for z.number().max()', () => {
        const zodSchema = 'z.number().max(100)';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('number');
        expect(result as number).toBeLessThanOrEqual(100);
      });

      it('generates integer for z.number().int()', () => {
        const zodSchema = 'z.number().int()';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('number');
        expect(Number.isInteger(result as number)).toBe(true);
      });

      it('generates number greater than for z.number().gt()', () => {
        const zodSchema = 'z.number().gt(5)';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('number');
        expect(result as number).toBeGreaterThan(5);
      });

      it('generates number less than for z.number().lt()', () => {
        const zodSchema = 'z.number().lt(10)';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('number');
        expect(result as number).toBeLessThan(10);
      });
    });

    describe('enum types', () => {
      it('generates mock for z.enum()', () => {
        const zodSchema = 'z.enum(["active", "inactive", "pending"])';
        const result = generateMockFromZodSchema(zodSchema);
        expect(['active', 'inactive', 'pending']).toContain(result);
      });

      it('generates mock for single value enum', () => {
        const zodSchema = 'z.enum(["single"])';
        const result = generateMockFromZodSchema(zodSchema);
        expect(result).toBe('single');
      });
    });

    describe('array types', () => {
      it('generates empty array when items schema cannot be parsed', () => {
        const zodSchema = 'z.array(z.string())';
        const result = generateMockFromZodSchema(zodSchema);
        expect(Array.isArray(result)).toBe(true);
      });

      it('generates array with overrides using items property', () => {
        const zodSchema = 'z.array(z.string())';
        const result = generateMockFromZodSchema(zodSchema, { items: ['a', 'b', 'c'] });
        expect(Array.isArray(result)).toBe(true);
        expect(result).toEqual(['a', 'b', 'c']);
      });

      it('respects maxItems constraint when specified', () => {
        const zodSchema = 'z.array(z.string()).max(2)';
        const result = generateMockFromZodSchema(zodSchema);
        expect(Array.isArray(result)).toBe(true);
        expect((result as unknown[]).length).toBeLessThanOrEqual(2);
      });

      it('generates array with default length between min and max', () => {
        const zodSchema = 'z.array(z.number())';
        const result = generateMockFromZodSchema(zodSchema);
        expect(Array.isArray(result)).toBe(true);

        expect((result as unknown[]).length).toBeGreaterThanOrEqual(1);
        expect((result as unknown[]).length).toBeLessThanOrEqual(3);
      });
    });

    describe('object types', () => {
      it('generates mock for z.object()', () => {
        const zodSchema = 'z.object({ name: z.string(), age: z.number() })';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('object');
        expect(result).toHaveProperty('name');
        expect(result).toHaveProperty('age');
        expect(typeof (result as Record<string, unknown>).name).toBe('string');
        expect(typeof (result as Record<string, unknown>).age).toBe('number');
      });

      it('handles optional fields', () => {
        const zodSchema = 'z.object({ name: z.string(), age: z.number().optional() })';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('object');
        expect(result).toHaveProperty('name');
      });

      it('always includes optionals when alwaysIncludeOptionals is true', () => {
        const zodSchema = 'z.object({ name: z.string(), age: z.number().optional() })';
        const result = generateMockFromZodSchema(zodSchema, {}, { alwaysIncludeOptionals: true });
        expect(typeof result).toBe('object');
        expect(result).toHaveProperty('name');
        expect(result).toHaveProperty('age');
      });

      it('handles nested objects', () => {
        const zodSchema = 'z.object({ user: z.object({ name: z.string(), email: z.string() }) })';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('object');
        expect(result).toHaveProperty('user');
        expect(typeof (result as Record<string, unknown>).user).toBe('object');
        expect((result as Record<string, Record<string, unknown>>).user).toHaveProperty('name');
        expect((result as Record<string, Record<string, unknown>>).user).toHaveProperty('email');
      });

      it('handles properties with special characters', () => {
        const zodSchema = 'z.object({ "user-name": z.string(), "my.email": z.string() })';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('object');
        expect(result).toHaveProperty('user-name');
        expect(result).toHaveProperty('my.email');
      });

      it('applies overrides to object properties', () => {
        const zodSchema = 'z.object({ name: z.string(), age: z.number() })';
        const result = generateMockFromZodSchema(zodSchema, { name: 'John', age: 30 });
        expect((result as Record<string, unknown>).name).toBe('John');
        expect((result as Record<string, unknown>).age).toBe(30);
      });

      it('applies partial overrides', () => {
        const zodSchema = 'z.object({ name: z.string(), age: z.number() })';
        const result = generateMockFromZodSchema(zodSchema, { name: 'John' });
        expect((result as Record<string, unknown>).name).toBe('John');
        expect(result).toHaveProperty('age');
        expect(typeof (result as Record<string, unknown>).age).toBe('number');
      });
    });

    describe('union types', () => {
      it('generates mock for z.union()', () => {
        const zodSchema = 'z.union([z.string(), z.number()])';
        const result = generateMockFromZodSchema(zodSchema);
        expect(['string', 'number']).toContain(typeof result);
      });

      it('handles nullable unions', () => {
        const zodSchema = 'z.union([z.string(), z.null()])';
        const result = generateMockFromZodSchema(zodSchema);
        expect(['string', 'object']).toContain(typeof result);
      });

      it('omits null in nullable unions when omitNulls is true', () => {
        const zodSchema = 'z.union([z.string(), z.null()])';
        const result = generateMockFromZodSchema(zodSchema, {}, { omitNulls: true });

        if (result === null) {
          expect(result).toBe(undefined);
        } else {
          expect(typeof result).toBe('string');
        }
      });
    });

    describe('complex nested schemas', () => {
      it('generates mock for complex object structure', () => {
        const zodSchema = `z.object({
          id: z.string().uuid(),
          user: z.object({
            name: z.string(),
            email: z.string().email(),
            age: z.number().int().min(0).max(120)
          }),
          tags: z.array(z.string()),
          status: z.enum(["active", "inactive"]),
          metadata: z.object({
            createdAt: z.string().datetime(),
            updatedAt: z.string().datetime()
          }).optional()
        })`;
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('object');
        expect(result).toHaveProperty('id');
        expect(result).toHaveProperty('user');
        expect(result).toHaveProperty('tags');
        expect(result).toHaveProperty('status');

        const obj = result as Record<string, unknown>;
        expect(typeof obj.id).toBe('string');
        expect(obj.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
        expect(Array.isArray(obj.tags)).toBe(true);
        expect(['active', 'inactive']).toContain(obj.status);
      });

      it('generates array using overrides for simple types', () => {
        const zodSchema = `z.array(z.number())`;
        const result = generateMockFromZodSchema(zodSchema, {
          items: [1, 2, 3],
        });
        expect(Array.isArray(result)).toBe(true);
        expect((result as unknown[]).length).toBe(3);
        expect(result).toEqual([1, 2, 3]);
      });
    });

    describe('options handling', () => {
      it('respects optionalsProbability option', () => {
        const zodSchema =
          'z.object({ optional1: z.string().optional(), optional2: z.string().optional(), optional3: z.string().optional() })';

        vi.spyOn(Math, 'random').mockReturnValue(0.9);
        const _result1 = generateMockFromZodSchema(zodSchema, {}, { optionalsProbability: 0 });

        vi.spyOn(Math, 'random').mockReturnValue(0.5);
        const _result2 = generateMockFromZodSchema(zodSchema, {}, { optionalsProbability: 1 });
      });

      it('handles optionalsProbability as false', () => {
        const zodSchema = 'z.object({ optional: z.string().optional() })';
        vi.spyOn(Math, 'random').mockReturnValue(0.5);
        const result = generateMockFromZodSchema(zodSchema, {}, { optionalsProbability: false });

        expect(typeof result).toBe('object');
      });
    });

    describe('edge cases', () => {
      it('handles empty object schema', () => {
        const zodSchema = 'z.object({})';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('object');
        expect(Object.keys(result as Record<string, unknown>).length).toBe(0);
      });

      it('handles unknown schema types', () => {
        const zodSchema = 'z.unknown()';
        const result = generateMockFromZodSchema(zodSchema);
        expect(result).toBe(null);
      });

      it('handles malformed schema strings gracefully', () => {
        const zodSchema = 'not a valid schema';
        const result = generateMockFromZodSchema(zodSchema);
        expect(result).toBe(null);
      });

      it('handles deeply nested property paths', () => {
        const zodSchema = `z.object({
          level1: z.object({
            level2: z.object({
              level3: z.object({
                value: z.string()
              })
            })
          })
        })`;
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('object');
        const obj = result as Record<string, unknown>;
        expect(obj).toHaveProperty('level1');
      });

      it('generates consistent mock values with overrides', () => {
        const zodSchema = 'z.object({ a: z.string(), b: z.string(), c: z.string() })';
        const overrides = { b: 'fixed-value' };
        const result = generateMockFromZodSchema(zodSchema, overrides);
        expect((result as Record<string, unknown>).b).toBe('fixed-value');
        expect(typeof (result as Record<string, unknown>).a).toBe('string');
        expect(typeof (result as Record<string, unknown>).c).toBe('string');
      });

      it('handles empty string schema', () => {
        const zodSchema = '';
        const result = generateMockFromZodSchema(zodSchema);
        expect(result).toBe(null);
      });

      it('handles string with only whitespace', () => {
        const zodSchema = '   ';
        const result = generateMockFromZodSchema(zodSchema);
        expect(result).toBe(null);
      });

      it('handles numeric enum values', () => {
        const zodSchema = 'z.enum([1, 2, 3])';
        const result = generateMockFromZodSchema(zodSchema);
        expect([1, 2, 3]).toContain(result);
      });

      it('handles boolean enum values', () => {
        const zodSchema = 'z.enum([true, false])';
        const result = generateMockFromZodSchema(zodSchema);
        expect([true, false]).toContain(result);
      });

      it('handles mixed type enum values', () => {
        const zodSchema = 'z.enum(["active", 1, true])';
        const result = generateMockFromZodSchema(zodSchema);
        expect(['active', 1, true]).toContain(result);
      });
    });

    describe('string regex patterns', () => {
      it('generates phone number for phone regex pattern', () => {
        const zodSchema = 'z.string().regex(/^\\+?[1-9]\\d{1,14}$/)';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('string');
        expect(result).toMatch(/^\+1\d{10}$/);
      });

      it('generates random string for custom regex pattern', () => {
        const zodSchema = 'z.string().regex(/^[A-Z]{5}$/)';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('string');
        expect((result as string).length).toBe(10);
      });
    });

    describe('string length constraints', () => {
      it('generates string respecting both min and max length', () => {
        const zodSchema = 'z.string().min(8).max(12)';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('string');
        const len = (result as string).length;
        expect(len).toBeGreaterThanOrEqual(8);
        expect(len).toBeLessThanOrEqual(12);
      });

      it('generates string with exact length when min equals max', () => {
        const zodSchema = 'z.string().min(10).max(10)';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('string');
        expect((result as string).length).toBe(10);
      });
    });

    describe('number constraints', () => {
      it('handles number with both min and max', () => {
        const zodSchema = 'z.number().min(10).max(20)';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('number');
        expect(result as number).toBeGreaterThanOrEqual(10);
        expect(result as number).toBeLessThanOrEqual(20);
      });

      it('handles number with both gt and lt', () => {
        const zodSchema = 'z.number().gt(5).lt(10)';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('number');
        expect(result as number).toBeGreaterThan(5);
        expect(result as number).toBeLessThan(10);
      });

      it('handles integer with constraints', () => {
        const zodSchema = 'z.number().int().min(1).max(10)';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('number');
        expect(Number.isInteger(result as number)).toBe(true);
        expect(result as number).toBeGreaterThanOrEqual(1);
        expect(result as number).toBeLessThanOrEqual(10);
      });

      it('generates negative numbers when range is negative', () => {
        const zodSchema = 'z.number().min(-100).max(-50)';
        const result = generateMockFromZodSchema(zodSchema);
        expect(typeof result).toBe('number');
        expect(result as number).toBeGreaterThanOrEqual(-100);
        expect(result as number).toBeLessThanOrEqual(-50);
      });
    });

    describe('union type variations', () => {
      it('handles union with multiple types', () => {
        const zodSchema = 'z.union([z.string(), z.number(), z.boolean()])';
        const result = generateMockFromZodSchema(zodSchema);
        expect(['string', 'number', 'boolean']).toContain(typeof result);
      });

      it('handles union with complex types', () => {
        const zodSchema = 'z.union([z.string().email(), z.number().int()])';
        const result = generateMockFromZodSchema(zodSchema);
        if (typeof result === 'string') {
          expect(result).toMatch(/^[\w.-]+@[\w.-]+\.\w+$/);
        } else {
          expect(Number.isInteger(result as number)).toBe(true);
        }
      });

      it('handles union with empty array returns null', () => {
        const zodSchema = 'z.union([])';
        const result = generateMockFromZodSchema(zodSchema);
        expect(result).toBe(null);
      });
    });

    describe('nullable types', () => {
      it('handles nullable string', () => {
        const zodSchema = 'z.string().nullable()';
        const result = generateMockFromZodSchema(zodSchema);
        expect(['string', 'object']).toContain(typeof result);
      });

      it('handles nullable number', () => {
        const zodSchema = 'z.number().nullable()';
        const result = generateMockFromZodSchema(zodSchema);
        expect(['number', 'object']).toContain(typeof result);
      });
    });
  });
});
