const fc = require('fast-check');

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

// Import the modules to test
const {
  languageMiddleware,
  parseAcceptLanguage,
  isValidLanguage,
  getLanguageWithFallback,
  SUPPORTED_LANGUAGES,
  DEFAULT_LANGUAGE,
} = require('../middleware/language');

const {
  capitalizeFirst,
  getLocalizedField,
  formatLocalizedObject,
  formatServiceResponse,
  formatCategoryResponse,
  formatNotificationResponse,
  getSystemMessage,
  systemMessages,
} = require('../utils/i18n');

describe('Multi-language Support Property Tests', () => {
  /**
   * Feature: overseas-appointment-backend, Property 54: Language-specific content
   * Validates: Requirements 12.2
   * 
   * For any user with a language preference, API responses should return content 
   * in that language.
   */
  describe('Property 54: Language-specific content', () => {
    test('getLocalizedField returns content in requested language when available', () => {
      // Generate non-whitespace strings to ensure valid content
      const nonWhitespaceString = fc.string({ minLength: 1, maxLength: 100 })
        .filter(s => s.trim().length > 0);
      
      fc.assert(
        fc.property(
          fc.record({
            titleZh: nonWhitespaceString,
            titleEn: nonWhitespaceString,
            titlePt: nonWhitespaceString,
          }),
          fc.constantFrom('zh', 'en', 'pt'),
          (obj, language) => {
            const result = getLocalizedField(obj, 'title', language);
            const langSuffix = capitalizeFirst(language);
            const expectedField = `title${langSuffix}`;
            
            // Should return the content in the requested language
            expect(result).toBe(obj[expectedField]);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('formatServiceResponse returns service with localized title and description', () => {
      // Generate non-whitespace strings for proper testing
      const nonWhitespaceString = fc.string({ minLength: 1, maxLength: 100 })
        .filter(s => s.trim().length > 0);
      
      fc.assert(
        fc.property(
          fc.record({
            id: fc.uuid(),
            categoryId: fc.uuid(),
            titleZh: nonWhitespaceString,
            titleEn: nonWhitespaceString,
            titlePt: nonWhitespaceString,
            descriptionZh: nonWhitespaceString,
            descriptionEn: nonWhitespaceString,
            descriptionPt: nonWhitespaceString,
            image: fc.webUrl(),
            price: fc.double({ min: 0, max: 10000, noNaN: true }),
            status: fc.constantFrom('active', 'inactive'),
            sortOrder: fc.integer({ min: 0, max: 100 }),
          }),
          fc.constantFrom('zh', 'en', 'pt'),
          (service, language) => {
            const result = formatServiceResponse(service, language);
            const langSuffix = capitalizeFirst(language);
            
            // Title should be in requested language
            expect(result.title).toBe(service[`title${langSuffix}`]);
            
            // Description should be in requested language
            expect(result.description).toBe(service[`description${langSuffix}`]);
            
            // Other fields should be preserved
            expect(result.id).toBe(service.id);
            expect(result.categoryId).toBe(service.categoryId);
            expect(result.image).toBe(service.image);
            expect(result.price).toBe(service.price);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('formatCategoryResponse returns category with localized name', () => {
      // Generate non-whitespace strings for proper testing
      const nonWhitespaceString = fc.string({ minLength: 1, maxLength: 100 })
        .filter(s => s.trim().length > 0);
      
      fc.assert(
        fc.property(
          fc.record({
            id: fc.uuid(),
            key: nonWhitespaceString,
            nameZh: nonWhitespaceString,
            nameEn: nonWhitespaceString,
            namePt: nonWhitespaceString,
            icon: nonWhitespaceString,
            sortOrder: fc.integer({ min: 0, max: 100 }),
          }),
          fc.constantFrom('zh', 'en', 'pt'),
          (category, language) => {
            const result = formatCategoryResponse(category, language);
            const langSuffix = capitalizeFirst(language);
            
            // Name should be in requested language
            expect(result.name).toBe(category[`name${langSuffix}`]);
            
            // Other fields should be preserved
            expect(result.id).toBe(category.id);
            expect(result.key).toBe(category.key);
            expect(result.icon).toBe(category.icon);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('formatNotificationResponse returns notification with localized title and content', () => {
      // Generate non-whitespace strings for proper testing
      const nonWhitespaceString = fc.string({ minLength: 1, maxLength: 100 })
        .filter(s => s.trim().length > 0);
      
      fc.assert(
        fc.property(
          fc.record({
            id: fc.uuid(),
            userId: fc.uuid(),
            type: fc.constantFrom('system', 'activity', 'service'),
            titleZh: nonWhitespaceString,
            titleEn: nonWhitespaceString,
            titlePt: nonWhitespaceString,
            contentZh: nonWhitespaceString,
            contentEn: nonWhitespaceString,
            contentPt: nonWhitespaceString,
            isRead: fc.boolean(),
            readAt: fc.option(fc.date()),
            createdAt: fc.date(),
            updatedAt: fc.date(),
          }),
          fc.constantFrom('zh', 'en', 'pt'),
          (notification, language) => {
            const result = formatNotificationResponse(notification, language);
            const langSuffix = capitalizeFirst(language);
            
            // Title should be in requested language
            expect(result.title).toBe(notification[`title${langSuffix}`]);
            
            // Content should be in requested language
            expect(result.content).toBe(notification[`content${langSuffix}`]);
            
            // Other fields should be preserved
            expect(result.id).toBe(notification.id);
            expect(result.userId).toBe(notification.userId);
            expect(result.type).toBe(notification.type);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('getSystemMessage returns message in requested language', () => {
      fc.assert(
        fc.property(
          fc.constantFrom(...Object.keys(systemMessages)),
          fc.constantFrom('zh', 'en', 'pt'),
          (messageKey, language) => {
            const result = getSystemMessage(messageKey, language);
            const expectedMessage = systemMessages[messageKey][language];
            
            // Should return the message in the requested language
            expect(result).toBe(expectedMessage);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('parseAcceptLanguage correctly parses Accept-Language header', () => {
      fc.assert(
        fc.property(
          fc.constantFrom('zh', 'en', 'pt'),
          fc.double({ min: 0.1, max: 1.0, noNaN: true }),
          (language, quality) => {
            // Create Accept-Language header with the language as primary
            const header = `${language};q=${quality.toFixed(1)}`;
            const result = parseAcceptLanguage(header);
            
            // Should return the language
            expect(result).toBe(language);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('isValidLanguage returns true for supported languages', () => {
      fc.assert(
        fc.property(
          fc.constantFrom(...SUPPORTED_LANGUAGES),
          (language) => {
            expect(isValidLanguage(language)).toBe(true);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('isValidLanguage returns false for unsupported languages', () => {
      fc.assert(
        fc.property(
          fc.string({ minLength: 2, maxLength: 5 }).filter(s => !SUPPORTED_LANGUAGES.includes(s)),
          (language) => {
            expect(isValidLanguage(language)).toBe(false);
          }
        ),
        { numRuns: 100 }
      );
    });
  });

  /**
   * Feature: overseas-appointment-backend, Property 57: Language fallback
   * Validates: Requirements 12.5
   * 
   * For any missing translation, the system should return the default language 
   * (Chinese) content instead.
   */
  describe('Property 57: Language fallback', () => {
    test('getLocalizedField falls back to Chinese when requested language is empty', () => {
      fc.assert(
        fc.property(
          fc.record({
            // Use string with at least one non-whitespace character to ensure valid fallback
            titleZh: fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
            titleEn: fc.constant(''),
            titlePt: fc.constant(''),
          }),
          fc.constantFrom('en', 'pt'),
          (obj, language) => {
            const result = getLocalizedField(obj, 'title', language);
            
            // Should fall back to Chinese
            expect(result).toBe(obj.titleZh);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('getLocalizedField falls back to Chinese when requested language field is whitespace only', () => {
      fc.assert(
        fc.property(
          fc.record({
            // Use string with at least one non-whitespace character to ensure valid fallback
            titleZh: fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
            titleEn: fc.stringOf(fc.constantFrom(' ', '\t', '\n'), { minLength: 0, maxLength: 5 }),
            titlePt: fc.stringOf(fc.constantFrom(' ', '\t', '\n'), { minLength: 0, maxLength: 5 }),
          }),
          fc.constantFrom('en', 'pt'),
          (obj, language) => {
            const result = getLocalizedField(obj, 'title', language);
            
            // Should fall back to Chinese since other languages are whitespace only
            expect(result).toBe(obj.titleZh);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('formatServiceResponse falls back to Chinese for missing translations', () => {
      // Generate non-whitespace strings for Chinese content
      const nonWhitespaceString = fc.string({ minLength: 1, maxLength: 100 })
        .filter(s => s.trim().length > 0);
      
      fc.assert(
        fc.property(
          fc.record({
            id: fc.uuid(),
            categoryId: fc.uuid(),
            titleZh: nonWhitespaceString,
            titleEn: fc.constant(''),
            titlePt: fc.constant(''),
            descriptionZh: nonWhitespaceString,
            descriptionEn: fc.constant(''),
            descriptionPt: fc.constant(''),
            image: fc.webUrl(),
            price: fc.double({ min: 0, max: 10000, noNaN: true }),
            status: fc.constantFrom('active', 'inactive'),
            sortOrder: fc.integer({ min: 0, max: 100 }),
          }),
          fc.constantFrom('en', 'pt'),
          (service, language) => {
            const result = formatServiceResponse(service, language);
            
            // Should fall back to Chinese
            expect(result.title).toBe(service.titleZh);
            expect(result.description).toBe(service.descriptionZh);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('formatCategoryResponse falls back to Chinese for missing translations', () => {
      // Generate non-whitespace strings for Chinese content
      const nonWhitespaceString = fc.string({ minLength: 1, maxLength: 100 })
        .filter(s => s.trim().length > 0);
      
      fc.assert(
        fc.property(
          fc.record({
            id: fc.uuid(),
            key: nonWhitespaceString,
            nameZh: nonWhitespaceString,
            nameEn: fc.constant(''),
            namePt: fc.constant(''),
            icon: nonWhitespaceString,
            sortOrder: fc.integer({ min: 0, max: 100 }),
          }),
          fc.constantFrom('en', 'pt'),
          (category, language) => {
            const result = formatCategoryResponse(category, language);
            
            // Should fall back to Chinese
            expect(result.name).toBe(category.nameZh);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('formatNotificationResponse falls back to Chinese for missing translations', () => {
      fc.assert(
        fc.property(
          fc.record({
            id: fc.uuid(),
            userId: fc.uuid(),
            type: fc.constantFrom('system', 'activity', 'service'),
            // Use string with at least one non-whitespace character to ensure valid fallback
            titleZh: fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
            titleEn: fc.constant(''),
            titlePt: fc.constant(''),
            contentZh: fc.string({ minLength: 1, maxLength: 500 }).filter(s => s.trim().length > 0),
            contentEn: fc.constant(''),
            contentPt: fc.constant(''),
            isRead: fc.boolean(),
            readAt: fc.option(fc.date()),
            createdAt: fc.date(),
            updatedAt: fc.date(),
          }),
          fc.constantFrom('en', 'pt'),
          (notification, language) => {
            const result = formatNotificationResponse(notification, language);
            
            // Should fall back to Chinese
            expect(result.title).toBe(notification.titleZh);
            expect(result.content).toBe(notification.contentZh);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('getLanguageWithFallback returns default language for invalid input', () => {
      fc.assert(
        fc.property(
          fc.string({ minLength: 0, maxLength: 10 }).filter(s => !SUPPORTED_LANGUAGES.includes(s)),
          (invalidLanguage) => {
            const result = getLanguageWithFallback(invalidLanguage);
            
            // Should return default language (Chinese)
            expect(result).toBe(DEFAULT_LANGUAGE);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('getLanguageWithFallback returns valid language when provided', () => {
      fc.assert(
        fc.property(
          fc.constantFrom(...SUPPORTED_LANGUAGES),
          (validLanguage) => {
            const result = getLanguageWithFallback(validLanguage);
            
            // Should return the valid language
            expect(result).toBe(validLanguage);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('parseAcceptLanguage returns null for unsupported languages', () => {
      fc.assert(
        fc.property(
          fc.string({ minLength: 2, maxLength: 5 }).filter(s => !SUPPORTED_LANGUAGES.includes(s.toLowerCase())),
          (unsupportedLang) => {
            const header = `${unsupportedLang};q=1.0`;
            const result = parseAcceptLanguage(header);
            
            // Should return null for unsupported language
            expect(result).toBeNull();
          }
        ),
        { numRuns: 100 }
      );
    });

    test('parseAcceptLanguage returns first supported language from complex header', () => {
      fc.assert(
        fc.property(
          fc.constantFrom('zh', 'en', 'pt'),
          (supportedLang) => {
            // Create header with unsupported language first, then supported
            const header = `fr;q=1.0,de;q=0.9,${supportedLang};q=0.8`;
            const result = parseAcceptLanguage(header);
            
            // Should return the supported language
            expect(result).toBe(supportedLang);
          }
        ),
        { numRuns: 100 }
      );
    });
  });
});
