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

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

// Mock service and category database
class MockServiceDatabase {
  constructor() {
    this.services = new Map();
    this.categories = new Map();
    this.initializeCategories();
  }

  initializeCategories() {
    const categories = [
      {
        id: 'cat-1',
        key: 'airport',
        nameZh: '机场服务',
        nameEn: 'Airport Service',
        namePt: 'Serviço de Aeroporto',
        icon: 'airport.png',
        sortOrder: 1,
      },
      {
        id: 'cat-2',
        key: 'hotel',
        nameZh: '酒店预订',
        nameEn: 'Hotel Booking',
        namePt: 'Reserva de Hotel',
        icon: 'hotel.png',
        sortOrder: 2,
      },
    ];

    categories.forEach(cat => this.categories.set(cat.id, cat));
  }

  clear() {
    this.services.clear();
    this.categories.clear();
    this.initializeCategories();
  }

  createService(serviceData) {
    const id = Math.random().toString(36).substring(7);
    const service = {
      id,
      status: 'active',
      sortOrder: 0,
      createdAt: new Date(),
      updatedAt: new Date(),
      ...serviceData,
    };
    this.services.set(id, service);
    return service;
  }

  findServiceById(id) {
    const service = this.services.get(id);
    if (!service) return null;

    // Include category information
    const category = this.categories.get(service.categoryId);
    return {
      ...service,
      category: category || null,
    };
  }

  findServices({ categoryId, page = 1, limit = 10 }) {
    let services = Array.from(this.services.values());

    // Filter by category if specified
    if (categoryId) {
      services = services.filter(s => s.categoryId === categoryId);
    }

    // Filter only active services
    services = services.filter(s => s.status === 'active');

    // Sort by sortOrder and createdAt
    services.sort((a, b) => {
      if (a.sortOrder !== b.sortOrder) {
        return a.sortOrder - b.sortOrder;
      }
      return b.createdAt - a.createdAt;
    });

    // Pagination
    const offset = (page - 1) * limit;
    const paginatedServices = services.slice(offset, offset + limit);

    // Include category information
    const servicesWithCategory = paginatedServices.map(service => ({
      ...service,
      category: this.categories.get(service.categoryId) || null,
    }));

    return {
      data: servicesWithCategory,
      total: services.length,
      page,
      limit,
      totalPages: Math.ceil(services.length / limit),
    };
  }

  getCategories() {
    return Array.from(this.categories.values()).sort((a, b) => a.sortOrder - b.sortOrder);
  }
}

// Mock service functions
const mockServiceService = {
  db: new MockServiceDatabase(),

  formatService: (service, language) => {
    const langSuffix = language.charAt(0).toUpperCase() + language.slice(1);
    
    const formatted = {
      id: service.id,
      categoryId: service.categoryId,
      title: service[`title${langSuffix}`] || service.titleZh,
      description: service[`description${langSuffix}`] || service.descriptionZh,
      image: service.image,
      price: service.price,
      status: service.status,
      sortOrder: service.sortOrder,
    };

    if (service.category) {
      formatted.category = {
        id: service.category.id,
        key: service.category.key,
        name: service.category[`name${langSuffix}`] || service.category.nameZh,
        icon: service.category.icon,
      };
    }

    return formatted;
  },

  formatCategory: (category, language) => {
    const langSuffix = language.charAt(0).toUpperCase() + language.slice(1);
    return {
      id: category.id,
      key: category.key,
      name: category[`name${langSuffix}`] || category.nameZh,
      icon: category.icon,
      sortOrder: category.sortOrder,
    };
  },

  getServiceById: async (serviceId, language = 'zh') => {
    const service = mockServiceService.db.findServiceById(serviceId);
    if (!service) {
      const error = new Error('Service not found');
      error.statusCode = 404;
      error.code = 'SERVICE_NOT_FOUND';
      throw error;
    }

    return mockServiceService.formatService(service, language);
  },

  getServices: async ({ page = 1, limit = 10, categoryId, language = 'zh' }) => {
    const result = mockServiceService.db.findServices({ categoryId, page, limit });
    
    return {
      data: result.data.map(service => mockServiceService.formatService(service, language)),
      pagination: {
        page: result.page,
        limit: result.limit,
        total: result.total,
        totalPages: result.totalPages,
      },
    };
  },

  getCategories: async (language = 'zh') => {
    const categories = mockServiceService.db.getCategories();
    return categories.map(cat => mockServiceService.formatCategory(cat, language));
  },
};

describe('Service Management Property Tests', () => {
  beforeEach(() => {
    mockServiceService.db.clear();
  });

  /**
   * Feature: overseas-appointment-backend, Property 8: Multi-language content retrieval
   * Validates: Requirements 2.3
   * 
   * For any service or category with multi-language content, requesting content 
   * in a specific language should return the correct translation.
   */
  describe('Property 8: Multi-language content retrieval', () => {
    test('Service content is returned in requested language', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.record({
            categoryId: fc.constantFrom('cat-1', 'cat-2'),
            titleZh: fc.string({ minLength: 5, maxLength: 50 }),
            titleEn: fc.string({ minLength: 5, maxLength: 50 }),
            titlePt: fc.string({ minLength: 5, maxLength: 50 }),
            descriptionZh: fc.string({ minLength: 10, maxLength: 200 }),
            descriptionEn: fc.string({ minLength: 10, maxLength: 200 }),
            descriptionPt: fc.string({ minLength: 10, maxLength: 200 }),
            image: fc.webUrl(),
            price: fc.double({ min: 0, max: 10000, noNaN: true }),
          }),
          fc.constantFrom('zh', 'en', 'pt'),
          async (serviceData, language) => {
            // Create a service with multi-language content
            const service = mockServiceService.db.createService(serviceData);

            // Retrieve service in specified language
            const result = await mockServiceService.getServiceById(service.id, language);

            // Verify the correct language content is returned
            const langSuffix = language.charAt(0).toUpperCase() + language.slice(1);
            expect(result.title).toBe(serviceData[`title${langSuffix}`]);
            expect(result.description).toBe(serviceData[`description${langSuffix}`]);

            // Verify category is also in correct language
            if (result.category) {
              const category = mockServiceService.db.categories.get(serviceData.categoryId);
              expect(result.category.name).toBe(category[`name${langSuffix}`]);
            }
          }
        ),
        { numRuns: 100 }
      );
    });

    test('Category content is returned in requested language', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.constantFrom('zh', 'en', 'pt'),
          async (language) => {
            // Get categories in specified language
            const categories = await mockServiceService.getCategories(language);

            // Verify all categories have correct language
            const langSuffix = language.charAt(0).toUpperCase() + language.slice(1);
            
            categories.forEach(category => {
              const originalCategory = Array.from(mockServiceService.db.categories.values())
                .find(c => c.id === category.id);
              
              expect(category.name).toBe(originalCategory[`name${langSuffix}`]);
            });
          }
        ),
        { numRuns: 100 }
      );
    });

    test('Service list returns all items in requested language', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.array(
            fc.record({
              categoryId: fc.constantFrom('cat-1', 'cat-2'),
              titleZh: fc.string({ minLength: 5, maxLength: 50 }),
              titleEn: fc.string({ minLength: 5, maxLength: 50 }),
              titlePt: fc.string({ minLength: 5, maxLength: 50 }),
              descriptionZh: fc.string({ minLength: 10, maxLength: 200 }),
              descriptionEn: fc.string({ minLength: 10, maxLength: 200 }),
              descriptionPt: fc.string({ minLength: 10, maxLength: 200 }),
              image: fc.webUrl(),
              price: fc.double({ min: 0, max: 10000, noNaN: true }),
            }),
            { minLength: 3, maxLength: 10 }
          ),
          fc.constantFrom('zh', 'en', 'pt'),
          async (servicesData, language) => {
            // Create multiple services
            servicesData.forEach(data => mockServiceService.db.createService(data));

            // Get service list in specified language
            const result = await mockServiceService.getServices({ language, limit: 20 });

            // Verify all services are in correct language
            const langSuffix = language.charAt(0).toUpperCase() + language.slice(1);
            
            result.data.forEach((service, index) => {
              // Find the original service data
              const originalService = Array.from(mockServiceService.db.services.values())
                .find(s => s.id === service.id);
              
              if (originalService) {
                expect(service.title).toBe(originalService[`title${langSuffix}`]);
                expect(service.description).toBe(originalService[`description${langSuffix}`]);
              }
            });
          }
        ),
        { numRuns: 50 }
      );
    });

    test('Language fallback to Chinese when translation missing', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.record({
            categoryId: fc.constantFrom('cat-1', 'cat-2'),
            titleZh: fc.string({ minLength: 5, maxLength: 50 }),
            titleEn: fc.constant(''),
            titlePt: fc.constant(''),
            descriptionZh: fc.string({ minLength: 10, maxLength: 200 }),
            descriptionEn: fc.constant(''),
            descriptionPt: fc.constant(''),
            image: fc.webUrl(),
            price: fc.double({ min: 0, max: 10000, noNaN: true }),
          }),
          fc.constantFrom('en', 'pt'),
          async (serviceData, language) => {
            // Create service with only Chinese content
            const service = mockServiceService.db.createService(serviceData);

            // Retrieve in non-Chinese language
            const result = await mockServiceService.getServiceById(service.id, language);

            // Should fall back to Chinese
            expect(result.title).toBe(serviceData.titleZh);
            expect(result.description).toBe(serviceData.descriptionZh);
          }
        ),
        { numRuns: 50 }
      );
    });
  });

  /**
   * Feature: overseas-appointment-backend, Property 9: Service filtering and pagination
   * Validates: Requirements 2.4
   * 
   * For any category filter and pagination parameters, the returned service list 
   * should contain only services from that category and respect the pagination limits.
   */
  describe('Property 9: Service filtering and pagination', () => {
    test('Category filter returns only services from that category', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.array(
            fc.record({
              categoryId: fc.constantFrom('cat-1', 'cat-2'),
              titleZh: fc.string({ minLength: 5, maxLength: 50 }),
              titleEn: fc.string({ minLength: 5, maxLength: 50 }),
              titlePt: fc.string({ minLength: 5, maxLength: 50 }),
              descriptionZh: fc.string({ minLength: 10, maxLength: 200 }),
              descriptionEn: fc.string({ minLength: 10, maxLength: 200 }),
              descriptionPt: fc.string({ minLength: 10, maxLength: 200 }),
              image: fc.webUrl(),
              price: fc.double({ min: 0, max: 10000, noNaN: true }),
            }),
            { minLength: 5, maxLength: 20 }
          ),
          fc.constantFrom('cat-1', 'cat-2'),
          async (servicesData, filterCategoryId) => {
            // Clear database for this iteration
            mockServiceService.db.clear();
            
            // Create multiple services with different categories
            servicesData.forEach(data => mockServiceService.db.createService(data));

            // Get services filtered by category
            const result = await mockServiceService.getServices({
              categoryId: filterCategoryId,
              limit: 50,
            });

            // Verify all returned services belong to the filtered category
            result.data.forEach(service => {
              expect(service.categoryId).toBe(filterCategoryId);
            });

            // Verify count matches expected
            const expectedCount = servicesData.filter(s => s.categoryId === filterCategoryId).length;
            expect(result.pagination.total).toBe(expectedCount);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('Pagination limits are respected', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.array(
            fc.record({
              categoryId: fc.constantFrom('cat-1', 'cat-2'),
              titleZh: fc.string({ minLength: 5, maxLength: 50 }),
              titleEn: fc.string({ minLength: 5, maxLength: 50 }),
              titlePt: fc.string({ minLength: 5, maxLength: 50 }),
              descriptionZh: fc.string({ minLength: 10, maxLength: 200 }),
              descriptionEn: fc.string({ minLength: 10, maxLength: 200 }),
              descriptionPt: fc.string({ minLength: 10, maxLength: 200 }),
              image: fc.webUrl(),
              price: fc.double({ min: 0, max: 10000, noNaN: true }),
            }),
            { minLength: 10, maxLength: 30 }
          ),
          fc.integer({ min: 1, max: 10 }),
          fc.integer({ min: 1, max: 3 }),
          async (servicesData, limit, page) => {
            // Clear database for this iteration
            mockServiceService.db.clear();
            
            // Create multiple services
            servicesData.forEach(data => mockServiceService.db.createService(data));

            // Get services with pagination
            const result = await mockServiceService.getServices({ page, limit });

            // Verify returned count respects limit
            expect(result.data.length).toBeLessThanOrEqual(limit);

            // Verify pagination metadata
            expect(result.pagination.page).toBe(page);
            expect(result.pagination.limit).toBe(limit);
            expect(result.pagination.total).toBe(servicesData.length);
            expect(result.pagination.totalPages).toBe(Math.ceil(servicesData.length / limit));

            // Verify we don't get more items than available
            const expectedItemsOnPage = Math.min(
              limit,
              Math.max(0, servicesData.length - (page - 1) * limit)
            );
            expect(result.data.length).toBe(expectedItemsOnPage);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('Pagination with category filter works correctly', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.array(
            fc.record({
              categoryId: fc.constantFrom('cat-1', 'cat-2'),
              titleZh: fc.string({ minLength: 5, maxLength: 50 }),
              titleEn: fc.string({ minLength: 5, maxLength: 50 }),
              titlePt: fc.string({ minLength: 5, maxLength: 50 }),
              descriptionZh: fc.string({ minLength: 10, maxLength: 200 }),
              descriptionEn: fc.string({ minLength: 10, maxLength: 200 }),
              descriptionPt: fc.string({ minLength: 10, maxLength: 200 }),
              image: fc.webUrl(),
              price: fc.double({ min: 0, max: 10000, noNaN: true }),
            }),
            { minLength: 10, maxLength: 30 }
          ),
          fc.constantFrom('cat-1', 'cat-2'),
          fc.integer({ min: 1, max: 5 }),
          async (servicesData, categoryId, limit) => {
            // Clear database for this iteration
            mockServiceService.db.clear();
            
            // Create multiple services
            servicesData.forEach(data => mockServiceService.db.createService(data));

            // Get services with both category filter and pagination
            const result = await mockServiceService.getServices({
              categoryId,
              page: 1,
              limit,
            });

            // Verify all services match category
            result.data.forEach(service => {
              expect(service.categoryId).toBe(categoryId);
            });

            // Verify pagination respects limit
            expect(result.data.length).toBeLessThanOrEqual(limit);

            // Verify total count matches category filter
            const expectedTotal = servicesData.filter(s => s.categoryId === categoryId).length;
            expect(result.pagination.total).toBe(expectedTotal);
          }
        ),
        { numRuns: 100 }
      );
    });

    test('Empty result when no services match filter', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.array(
            fc.record({
              categoryId: fc.constant('cat-1'),
              titleZh: fc.string({ minLength: 5, maxLength: 50 }),
              titleEn: fc.string({ minLength: 5, maxLength: 50 }),
              titlePt: fc.string({ minLength: 5, maxLength: 50 }),
              descriptionZh: fc.string({ minLength: 10, maxLength: 200 }),
              descriptionEn: fc.string({ minLength: 10, maxLength: 200 }),
              descriptionPt: fc.string({ minLength: 10, maxLength: 200 }),
              image: fc.webUrl(),
              price: fc.double({ min: 0, max: 10000, noNaN: true }),
            }),
            { minLength: 3, maxLength: 10 }
          ),
          async (servicesData) => {
            // Create services only in cat-1
            servicesData.forEach(data => mockServiceService.db.createService(data));

            // Query for cat-2 (should be empty)
            const result = await mockServiceService.getServices({
              categoryId: 'cat-2',
              limit: 10,
            });

            // Verify empty result
            expect(result.data).toHaveLength(0);
            expect(result.pagination.total).toBe(0);
            expect(result.pagination.totalPages).toBe(0);
          }
        ),
        { numRuns: 50 }
      );
    });

    test('Services are ordered by sortOrder then createdAt', async () => {
      await fc.assert(
        fc.asyncProperty(
          fc.array(
            fc.record({
              categoryId: fc.constantFrom('cat-1', 'cat-2'),
              titleZh: fc.string({ minLength: 5, maxLength: 50 }),
              titleEn: fc.string({ minLength: 5, maxLength: 50 }),
              titlePt: fc.string({ minLength: 5, maxLength: 50 }),
              descriptionZh: fc.string({ minLength: 10, maxLength: 200 }),
              descriptionEn: fc.string({ minLength: 10, maxLength: 200 }),
              descriptionPt: fc.string({ minLength: 10, maxLength: 200 }),
              image: fc.webUrl(),
              price: fc.double({ min: 0, max: 10000, noNaN: true }),
              sortOrder: fc.integer({ min: 0, max: 10 }),
            }),
            { minLength: 5, maxLength: 15 }
          ),
          async (servicesData) => {
            // Create services with different sortOrders
            servicesData.forEach(data => mockServiceService.db.createService(data));

            // Get all services
            const result = await mockServiceService.getServices({ limit: 50 });

            // Verify ordering
            for (let i = 1; i < result.data.length; i++) {
              const prev = result.data[i - 1];
              const curr = result.data[i];

              // Should be ordered by sortOrder ascending
              expect(prev.sortOrder).toBeLessThanOrEqual(curr.sortOrder);
            }
          }
        ),
        { numRuns: 50 }
      );
    });
  });
});
