import "reflect-metadata";
import * as fc from "fast-check";
import { 
  MetadataRegistry,
  SchemaBuilder,
  Entity, 
  Property,
  Id
} from "../../core";
import { SchemaReflector } from "../../core/SchemaReflector";

// Define a basic entity class for testing
@Entity()
class TestEntity {
  @Id()
  id: string;

  @Property({ required: true })
  requiredString: string;

  @Property({ required: false })
  optionalString?: string;

  @Property({ required: true })
  requiredNumber: number;

  @Property({ required: false })
  optionalNumber?: number;

  @Property({ required: true })
  requiredBoolean: boolean;

  @Property({ required: false })
  optionalBoolean?: boolean;

  @Property({ required: true })
  requiredDate: Date;

  @Property({ required: false })
  optionalDate?: Date;
}

describe("Property-based Validation Tests", () => {
  let registry: MetadataRegistry;
  let builder: SchemaBuilder;
  let reflector: SchemaReflector;

  beforeEach(() => {
    registry = new MetadataRegistry();
    builder = new SchemaBuilder(registry);
    reflector = new SchemaReflector(registry);
    builder.registerEntity(TestEntity);
  });

  test("validation should pass for valid entities", () => {
    // Define arbitrary generators for each type
    const idArb = fc.string({ minLength: 1 });
    const stringArb = fc.string();
    const numberArb = fc.double();
    const booleanArb = fc.boolean();
    const dateArb = fc.date();
    
    // Define an arbitrary for the whole entity
    const entityArb = fc.record({
      id: idArb,
      requiredString: stringArb,
      optionalString: fc.option(stringArb),
      requiredNumber: numberArb,
      optionalNumber: fc.option(numberArb),
      requiredBoolean: booleanArb,
      optionalBoolean: fc.option(booleanArb),
      requiredDate: dateArb,
      optionalDate: fc.option(dateArb),
    });
    
    // Test 100 different valid entity configurations
    fc.assert(
      fc.property(entityArb, (entityData) => {
        // Create entity instance
        const entity = new TestEntity();
        Object.assign(entity, entityData);
        
        // Validate entity
        const result = reflector.validateEntity(entity);
        
        // Should be valid
        return result.valid && result.errors.length === 0;
      }),
      { numRuns: 100 }
    );
  });

  test("validation should fail if required properties are missing", () => {
    // Test for each required field one at a time
    const requiredFields = ['requiredString', 'requiredNumber', 'requiredBoolean', 'requiredDate'];
    
    fc.assert(
      fc.property(fc.constantFrom(...requiredFields), (fieldToRemove) => {
        // Create a complete entity
        const entity = new TestEntity();
        entity.id = "123";
        entity.requiredString = "test";
        entity.requiredNumber = 42;
        entity.requiredBoolean = true;
        entity.requiredDate = new Date();
        
        // Remove the specified field
        delete (entity as any)[fieldToRemove];
        
        // Validate entity
        const result = reflector.validateEntity(entity);
        
        // Should be invalid with exactly one error
        if (!result.valid && result.errors.length === 1) {
          // The error should mention the missing field
          return result.errors[0].includes(fieldToRemove);
        }
        return false;
      }),
      { numRuns: 10 }
    );
  });

  test("validation should handle edge cases for required properties", () => {
    fc.assert(
      fc.property(
        fc.record({
          // Generate only empty strings, zero values, false values
          requiredString: fc.constant(""),
          requiredNumber: fc.constant(0),
          requiredBoolean: fc.constant(false),
          requiredDate: fc.date()
        }),
        (values) => {
          // Create entity with edge-case values
          const entity = new TestEntity();
          entity.id = "123";
          Object.assign(entity, values);
          
          // Validate entity - empty string, 0, and false are valid values
          // They should pass validation since they're not undefined or null
          const result = reflector.validateEntity(entity);
          
          return result.valid && result.errors.length === 0;
        }
      ),
      { numRuns: 50 }
    );
  });

  test("validation should handle null vs undefined", () => {
    fc.assert(
      fc.property(
        fc.record({
          // For all optional fields, test with null
          optionalString: fc.constant(null),
          optionalNumber: fc.constant(null),
          optionalBoolean: fc.constant(null),
          optionalDate: fc.constant(null),
        }),
        (nullValues) => {
          // Create a complete entity
          const entity = new TestEntity();
          entity.id = "123";
          entity.requiredString = "test";
          entity.requiredNumber = 42;
          entity.requiredBoolean = true;
          entity.requiredDate = new Date();
          
          // Add null values for optional fields
          Object.assign(entity, nullValues);
          
          // Validate entity - null should be treated as missing
          const result = reflector.validateEntity(entity);
          
          // Should still be valid since these are optional fields
          return result.valid && result.errors.length === 0;
        }
      ),
      { numRuns: 10 }
    );
  });

  test("validation should work with complex object structures", () => {
    // Create a new entity class with nested objects
    @Entity()
    class ComplexEntity {
      @Id()
      id: string;
      
      @Property({ required: true })
      metadata: {
        created: Date;
        tags: string[];
        settings: {
          enabled: boolean;
          level: number;
        }
      };
    }
    
    // Register the complex entity
    builder.registerEntity(ComplexEntity);
    
    // Define arbitrary generators for the complex structure
    const complexEntityArb = fc.record({
      id: fc.string({ minLength: 1 }),
      metadata: fc.record({
        created: fc.date(),
        tags: fc.array(fc.string()),
        settings: fc.record({
          enabled: fc.boolean(),
          level: fc.integer()
        })
      })
    });
    
    // Test validation with complex structures
    fc.assert(
      fc.property(complexEntityArb, (entityData) => {
        // Create entity instance
        const entity = new ComplexEntity();
        Object.assign(entity, entityData);
        
        // Validate entity
        const result = reflector.validateEntity(entity);
        
        // Should be valid
        return result.valid && result.errors.length === 0;
      }),
      { numRuns: 50 }
    );
  });
});