const fc = require('fast-check');
const { generateToken, verifyToken } = require('../utils/jwt');

/**
 * Property-Based Tests for Authentication System
 * Feature: overseas-appointment-backend
 * 
 * These tests verify authentication properties using property-based testing
 * to ensure correctness across a wide range of inputs.
 */

describe('Authentication Property Tests', () => {

  /**
   * Feature: overseas-appointment-backend, Property 1: Valid credentials produce valid JWT tokens
   * Validates: Requirements 1.1
   * 
   * For any valid WeChat credentials, authenticating should return a JWT token 
   * that can be successfully verified and decoded to retrieve the user information.
   */
  describe('Property 1: Valid credentials produce valid JWT tokens', () => {
    test('Generated tokens can be verified and decoded to retrieve user information', () => {
      fc.assert(
        fc.property(
          fc.record({
            userId: fc.uuid(),
            type: fc.constant('user'),
          }),
          (payload) => {
            // Generate token
            const token = generateToken(payload);

            // Verify token was generated
            expect(token).toBeDefined();
            expect(typeof token).toBe('string');
            expect(token.split('.').length).toBe(3); // JWT has 3 parts

            // Verify token can be decoded
            const decoded = verifyToken(token);
            
            // Verify decoded token contains correct user information
            expect(decoded.userId).toBe(payload.userId);
            expect(decoded.type).toBe(payload.type);
            
            // Verify token has expiration
            expect(decoded.exp).toBeDefined();
            expect(decoded.exp).toBeGreaterThan(Math.floor(Date.now() / 1000));
            
            // Verify token has issued at timestamp
            expect(decoded.iat).toBeDefined();
            expect(decoded.iat).toBeLessThanOrEqual(Math.floor(Date.now() / 1000));
          }
        ),
        { numRuns: 100 }
      );
    });

    test('Tokens with different payloads produce different tokens', () => {
      fc.assert(
        fc.property(
          fc.tuple(
            fc.record({ userId: fc.uuid(), type: fc.constant('user') }),
            fc.record({ userId: fc.uuid(), type: fc.constant('user') })
          ).filter(([p1, p2]) => p1.userId !== p2.userId),
          ([payload1, payload2]) => {
            // Generate two tokens with different payloads
            const token1 = generateToken(payload1);
            const token2 = generateToken(payload2);

            // Verify tokens are different
            expect(token1).not.toBe(token2);

            // Verify each token decodes to its original payload
            const decoded1 = verifyToken(token1);
            const decoded2 = verifyToken(token2);

            expect(decoded1.userId).toBe(payload1.userId);
            expect(decoded2.userId).toBe(payload2.userId);
          }
        ),
        { numRuns: 50 }
      );
    });
  });

  /**
   * Feature: overseas-appointment-backend, Property 2: Invalid tokens are rejected
   * Validates: Requirements 1.2
   * 
   * For any expired or malformed JWT token, API requests using that token 
   * should be rejected with a 401 status code.
   */
  describe('Property 2: Invalid tokens are rejected', () => {
    test('Malformed tokens are rejected', () => {
      fc.assert(
        fc.property(
          fc.oneof(
            fc.string({ minLength: 0, maxLength: 20 }), // Too short
            fc.string({ minLength: 1, maxLength: 100 }).filter(s => !s.includes('.')), // No dots
            fc.constant(''), // Empty string
            fc.constant('invalid.token'), // Invalid format
            fc.constant('not.a.jwt.token.at.all'), // Wrong structure
          ),
          (invalidToken) => {
            // Attempt to verify invalid token
            expect(() => {
              verifyToken(invalidToken);
            }).toThrow();
          }
        ),
        { numRuns: 100 }
      );
    });

    test('Tokens with wrong secret are rejected', () => {
      fc.assert(
        fc.property(
          fc.record({
            userId: fc.uuid(),
            type: fc.constant('user'),
          }),
          (payload) => {
            // Generate token with wrong secret
            const jwt = require('jsonwebtoken');
            const wrongToken = jwt.sign(payload, 'wrong-secret-key', { expiresIn: '1h' });

            // Attempt to verify with correct secret should fail
            expect(() => {
              verifyToken(wrongToken);
            }).toThrow('Invalid token');
          }
        ),
        { numRuns: 50 }
      );
    });

    test('Expired tokens are rejected', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.record({
            userId: fc.uuid(),
            type: fc.constant('user'),
          }),
          async (payload) => {
            // Generate token that expires in 1 millisecond
            const expiredToken = generateToken(payload, { expiresIn: '1ms' });

            // Wait for token to expire
            await new Promise((resolve) => setTimeout(resolve, 100));

            // Attempt to verify expired token should throw
            expect(() => {
              verifyToken(expiredToken);
            }).toThrow('Token has expired');
          }
        ),
        { numRuns: 20 }
      );
    });
  });

  /**
   * Feature: overseas-appointment-backend, Property 3: Login creates audit records
   * Validates: Requirements 1.3
   * 
   * For any successful login, the system should create a login record containing 
   * timestamp and device information.
   * 
   * Note: This property is tested through integration tests with real database.
   * Here we verify the structure and requirements of login audit data.
   */
  describe('Property 3: Login creates audit records', () => {
    test('Login audit records contain required fields', () => {
      fc.assert(
        fc.property(
          fc.record({
            userId: fc.uuid(),
            userType: fc.constantFrom('user', 'admin'),
            ipAddress: fc.ipV4(),
            userAgent: fc.string({ minLength: 10, maxLength: 200 }),
            deviceInfo: fc.record({
              platform: fc.constantFrom('ios', 'android', 'web'),
              version: fc.string({ minLength: 1, maxLength: 20 }),
            }),
            loginAt: fc.date(),
          }),
          (auditRecord) => {
            // Verify all required fields are present
            expect(auditRecord.userId).toBeDefined();
            expect(auditRecord.userType).toBeDefined();
            expect(auditRecord.loginAt).toBeDefined();
            
            // Verify userId is a valid UUID format
            expect(auditRecord.userId).toMatch(
              /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
            );
            
            // Verify userType is valid
            expect(['user', 'admin']).toContain(auditRecord.userType);
            
            // Verify loginAt is a valid date
            expect(auditRecord.loginAt).toBeInstanceOf(Date);
            
            // Verify optional fields have correct types when present
            if (auditRecord.ipAddress) {
              expect(typeof auditRecord.ipAddress).toBe('string');
            }
            if (auditRecord.userAgent) {
              expect(typeof auditRecord.userAgent).toBe('string');
            }
            if (auditRecord.deviceInfo) {
              expect(typeof auditRecord.deviceInfo).toBe('object');
            }
          }
        ),
        { numRuns: 100 }
      );
    });
  });
});
