/**
 * Property-Based Tests: Config Wizard Validation Functions
 *
 * Feature: config-wizard
 *
 * Tests the pure validation functions from `configure.js`.
 *
 * Validates: Requirements 5.1, 5.2, 5.3, 5.4, 5.6, 5.7
 */

const fc = require('fast-check');
const {
  validatePort,
  validateCorsOrigins,
  validateDatabaseUrl,
  validateSessionSecret,
  validateRequired
} = require('../../configure.js');

// --- Property 8: Port validation ---
describe('Property 8: Port validation', () => {
  /**
   * **Validates: Requirements 5.2**
   *
   * For any string, validatePort returns true iff the trimmed value is an integer
   * in [1, 65535] with no leading zeros.
   */
  test('validatePort returns true iff trimmed value is integer in [1, 65535] with no leading zeros', () => {
    fc.assert(
      fc.property(fc.string(), (input) => {
        const result = validatePort(input);
        const trimmed = input.trim();

        // Compute expected result
        if (trimmed === '') return result === false;
        const parsed = parseInt(trimmed, 10);
        if (isNaN(parsed)) return result === false;
        // Must be exact string representation (no leading zeros, no floats, no extra chars)
        if (trimmed !== String(parsed)) return result === false;
        const expected = parsed >= 1 && parsed <= 65535;
        return result === expected;
      }),
      { numRuns: 100 }
    );
  });

  test('validatePort returns true for valid port numbers', () => {
    fc.assert(
      fc.property(fc.integer({ min: 1, max: 65535 }), (port) => {
        return validatePort(String(port)) === true;
      }),
      { numRuns: 100 }
    );
  });

  test('validatePort returns false for out-of-range integers', () => {
    fc.assert(
      fc.property(
        fc.oneof(
          fc.integer({ min: 65536, max: 999999 }),
          fc.integer({ min: -999999, max: 0 })
        ),
        (port) => {
          return validatePort(String(port)) === false;
        }
      ),
      { numRuns: 100 }
    );
  });

  test('validatePort rejects leading zeros', () => {
    fc.assert(
      fc.property(fc.integer({ min: 1, max: 9999 }), (port) => {
        const withLeadingZero = '0' + String(port);
        return validatePort(withLeadingZero) === false;
      }),
      { numRuns: 100 }
    );
  });
});

// --- Property 9: CORS origins validation ---
describe('Property 9: CORS origins validation', () => {
  /**
   * **Validates: Requirements 5.3, 5.7**
   *
   * For any comma-separated string, validateCorsOrigins returns true iff at least
   * one valid entry remains after trim/discard and each starts with http:// or
   * https:// followed by non-whitespace.
   */
  test('validateCorsOrigins returns true iff at least one valid entry remains after trim/discard', () => {
    fc.assert(
      fc.property(fc.string(), (input) => {
        const result = validateCorsOrigins(input);

        // Compute expected
        const entries = input.split(',')
          .map(entry => entry.trim())
          .filter(entry => entry.length > 0);

        if (entries.length === 0) return result === false;

        const allValid = entries.every(entry => /^https?:\/\/\S+/.test(entry));
        return result === allValid;
      }),
      { numRuns: 100 }
    );
  });

  test('validateCorsOrigins accepts valid http/https origins', () => {
    const validOriginArb = fc.oneof(
      fc.webUrl().map(url => url.split('/').slice(0, 3).join('/')),
      fc.constantFrom(
        'http://localhost:3000',
        'https://example.com',
        'http://192.168.1.1:8080'
      )
    );

    fc.assert(
      fc.property(
        fc.array(validOriginArb, { minLength: 1, maxLength: 5 }),
        (origins) => {
          return validateCorsOrigins(origins.join(',')) === true;
        }
      ),
      { numRuns: 100 }
    );
  });

  test('validateCorsOrigins rejects entries without http/https prefix', () => {
    const invalidOriginArb = fc.stringMatching(/^[a-z][a-z0-9]*:\/\/\S+/, { minLength: 4, maxLength: 30 })
      .filter(s => !s.startsWith('http://') && !s.startsWith('https://'));

    fc.assert(
      fc.property(invalidOriginArb, (origin) => {
        return validateCorsOrigins(origin) === false;
      }),
      { numRuns: 100 }
    );
  });
});

// --- Property 10: DATABASE_URL validation ---
describe('Property 10: DATABASE_URL validation', () => {
  /**
   * **Validates: Requirements 5.4**
   *
   * For any string, validateDatabaseUrl returns true iff it starts with
   * `postgresql://` or equals `sqlite`.
   */
  test('validateDatabaseUrl returns true iff starts with postgresql:// or equals sqlite', () => {
    fc.assert(
      fc.property(fc.string(), (input) => {
        const result = validateDatabaseUrl(input);
        const expected = input.startsWith('postgresql://') || input === 'sqlite';
        return result === expected;
      }),
      { numRuns: 100 }
    );
  });

  test('validateDatabaseUrl accepts any postgresql:// URL', () => {
    fc.assert(
      fc.property(fc.string({ minLength: 0, maxLength: 100 }), (suffix) => {
        return validateDatabaseUrl('postgresql://' + suffix) === true;
      }),
      { numRuns: 100 }
    );
  });

  test('validateDatabaseUrl accepts sqlite literal', () => {
    expect(validateDatabaseUrl('sqlite')).toBe(true);
  });

  test('validateDatabaseUrl rejects other strings', () => {
    fc.assert(
      fc.property(
        fc.string({ minLength: 1, maxLength: 50 }).filter(
          s => !s.startsWith('postgresql://') && s !== 'sqlite'
        ),
        (input) => {
          return validateDatabaseUrl(input) === false;
        }
      ),
      { numRuns: 100 }
    );
  });
});

// --- Property 11: SESSION_SECRET validation ---
describe('Property 11: SESSION_SECRET validation', () => {
  /**
   * **Validates: Requirements 5.6**
   *
   * For any string, validateSessionSecret returns true iff length in [16, 256].
   */
  test('validateSessionSecret returns true iff length in [16, 256]', () => {
    fc.assert(
      fc.property(fc.string({ minLength: 0, maxLength: 300 }), (input) => {
        const result = validateSessionSecret(input);
        const expected = input.length >= 16 && input.length <= 256;
        return result === expected;
      }),
      { numRuns: 100 }
    );
  });

  test('validateSessionSecret accepts strings of length 16-256', () => {
    fc.assert(
      fc.property(
        fc.integer({ min: 16, max: 256 }).chain(len =>
          fc.string({ minLength: len, maxLength: len })
        ),
        (input) => {
          return validateSessionSecret(input) === true;
        }
      ),
      { numRuns: 100 }
    );
  });

  test('validateSessionSecret rejects strings shorter than 16', () => {
    fc.assert(
      fc.property(fc.string({ minLength: 0, maxLength: 15 }), (input) => {
        return validateSessionSecret(input) === false;
      }),
      { numRuns: 100 }
    );
  });
});

// --- Property 12: Required variable rejection of whitespace ---
describe('Property 12: Required variable rejection of whitespace', () => {
  /**
   * **Validates: Requirements 5.1**
   *
   * For any whitespace-only string, validateRequired returns false;
   * for any string with non-whitespace, returns true.
   */
  test('validateRequired returns false for whitespace-only strings', () => {
    const whitespaceArb = fc.array(
      fc.constantFrom(' ', '\t', '\n', '\r', '\f', '\v'),
      { minLength: 0, maxLength: 20 }
    ).map(chars => chars.join(''));

    fc.assert(
      fc.property(whitespaceArb, (input) => {
        return validateRequired(input) === false;
      }),
      { numRuns: 100 }
    );
  });

  test('validateRequired returns true for strings with non-whitespace', () => {
    fc.assert(
      fc.property(
        fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
        (input) => {
          return validateRequired(input) === true;
        }
      ),
      { numRuns: 100 }
    );
  });

  test('validateRequired equivalence: result matches trim().length > 0', () => {
    fc.assert(
      fc.property(fc.string(), (input) => {
        const result = validateRequired(input);
        const expected = input.trim().length > 0;
        return result === expected;
      }),
      { numRuns: 100 }
    );
  });
});
