/**
 * ImageAttachment Component Property-Based Tests
 * Feature: accounting-feature-upgrade
 * Property 8: Image deletion consistency
 * Validates: Requirements 4.7
 */

import { describe, it, expect, vi, afterEach } from 'vitest';
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
import fc from 'fast-check';
import { ImageAttachment } from './ImageAttachment';
import type { TransactionImage } from '../../../types';

describe('ImageAttachment Property Tests', () => {
  // Clean up after each test to avoid DOM pollution
  afterEach(() => {
    cleanup();
  });
  /**
   * Property 8: Image deletion consistency
   * For any image list and any delete operation, after deletion:
   * - The image should be removed from the list
   * - The list length should decrease by 1
   * 
   * **Validates: Requirements 4.7**
   */
  it('Property 8: should maintain deletion consistency', () => {
    fc.assert(
      fc.property(
        // Generate array of 1-9 images
        fc.array(
          fc.record({
            id: fc.integer({ min: 1, max: 10000 }),
            transactionId: fc.constant(1),
            filePath: fc.string(),
            fileName: fc.string(),
            fileSize: fc.integer({ min: 1, max: 10485760 }),
            mimeType: fc.constantFrom('image/jpeg', 'image/png', 'image/heic'),
            createdAt: fc.constant('2024-01-01T00:00:00Z'),
          }),
          { minLength: 1, maxLength: 9 }
        ),
        // Generate index to delete
        fc.nat(),
        (images, deleteIndexRaw) => {
          // Clean up before each iteration
          cleanup();

          // Ensure unique IDs
          const uniqueImages = images.map((img, idx) => ({
            ...img,
            id: idx + 1,
          }));

          const deleteIndex = deleteIndexRaw % uniqueImages.length;
          const imageToDelete = uniqueImages[deleteIndex];

          // Track deletion
          let deletedImageId: number | null = null;
          const mockOnRemove = (imageId: number) => {
            deletedImageId = imageId;
          };

          const mockOnAdd = vi.fn();
          const mockOnPreview = vi.fn();

          // Render component
          const { rerender, container } = render(
            <ImageAttachment
              images={uniqueImages}
              onAdd={mockOnAdd}
              onRemove={mockOnRemove}
              onPreview={mockOnPreview}
            />
          );

          // Verify initial state
          const initialCount = uniqueImages.length;
          const countElement = container.querySelector('.image-attachment__count');
          expect(countElement?.textContent?.trim().replace(/\s+/g, ' ')).toBe(`${initialCount} / 9`);

          // Find and click delete button for the target image
          const deleteButtons = container.querySelectorAll('[aria-label="删除图片"]');
          fireEvent.click(deleteButtons[deleteIndex]);

          // Verify onRemove was called with correct ID
          expect(deletedImageId).toBe(imageToDelete.id);

          // Simulate state update after deletion
          const updatedImages = uniqueImages.filter((img) => img.id !== imageToDelete.id);

          rerender(
            <ImageAttachment
              images={updatedImages}
              onAdd={mockOnAdd}
              onRemove={mockOnRemove}
              onPreview={mockOnPreview}
            />
          );

          // Property verification:
          // 1. List length should decrease by 1
          const newCount = updatedImages.length;
          expect(newCount).toBe(initialCount - 1);
          
          const newCountElement = container.querySelector('.image-attachment__count');
          expect(newCountElement?.textContent?.trim().replace(/\s+/g, ' ')).toBe(`${newCount} / 9`);

          // 2. Deleted image should not be in the list
          const remainingImages = container.querySelectorAll('img');
          expect(remainingImages).toHaveLength(newCount);

          // 3. All remaining images should be different from deleted image
          const remainingIds = updatedImages.map((img) => img.id);
          expect(remainingIds).not.toContain(imageToDelete.id);

          // Clean up after iteration
          cleanup();

          return true;
        }
      ),
      { numRuns: 100 }
    );
  });

  /**
   * Property: Image count display consistency
   * For any valid image array (0-9 images), the displayed count should match the array length
   * 
   * **Validates: Requirements 4.9**
   */
  it('Property: should display correct image count for any valid array', () => {
    fc.assert(
      fc.property(
        fc.array(
          fc.record({
            id: fc.integer({ min: 1, max: 10000 }),
            transactionId: fc.constant(1),
            filePath: fc.string(),
            fileName: fc.string(),
            fileSize: fc.integer({ min: 1, max: 10485760 }),
            mimeType: fc.constantFrom('image/jpeg', 'image/png', 'image/heic'),
            createdAt: fc.constant('2024-01-01T00:00:00Z'),
          }),
          { minLength: 0, maxLength: 9 }
        ),
        (images) => {
          // Clean up before each iteration
          cleanup();

          // Ensure unique IDs
          const uniqueImages = images.map((img, idx) => ({
            ...img,
            id: idx + 1,
          }));

          const mockOnAdd = vi.fn();
          const mockOnRemove = vi.fn();
          const mockOnPreview = vi.fn();

          const { container } = render(
            <ImageAttachment
              images={uniqueImages}
              onAdd={mockOnAdd}
              onRemove={mockOnRemove}
              onPreview={mockOnPreview}
            />
          );

          // Verify count display
          const expectedCount = uniqueImages.length;
          const countElement = container.querySelector('.image-attachment__count');
          expect(countElement?.textContent?.trim().replace(/\s+/g, ' ')).toBe(`${expectedCount} / 9`);

          // Verify actual rendered images match count
          const renderedImages = container.querySelectorAll('img');
          expect(renderedImages).toHaveLength(expectedCount);

          // Clean up after iteration
          cleanup();

          return true;
        }
      ),
      { numRuns: 100 }
    );
  });

  /**
   * Property: Add button visibility based on image count
   * For any image array, add button should be visible if count < 9, hidden if count >= 9
   * 
   * **Validates: Requirements 4.9, 4.12**
   */
  it('Property: should show/hide add button based on image count', () => {
    fc.assert(
      fc.property(
        fc.array(
          fc.record({
            id: fc.integer({ min: 1, max: 10000 }),
            transactionId: fc.constant(1),
            filePath: fc.string(),
            fileName: fc.string(),
            fileSize: fc.integer({ min: 1, max: 10485760 }),
            mimeType: fc.constantFrom('image/jpeg', 'image/png', 'image/heic'),
            createdAt: fc.constant('2024-01-01T00:00:00Z'),
          }),
          { minLength: 0, maxLength: 9 }
        ),
        (images) => {
          // Clean up before each iteration
          cleanup();

          // Ensure unique IDs
          const uniqueImages = images.map((img, idx) => ({
            ...img,
            id: idx + 1,
          }));

          const mockOnAdd = vi.fn();
          const mockOnRemove = vi.fn();
          const mockOnPreview = vi.fn();

          const { container } = render(
            <ImageAttachment
              images={uniqueImages}
              onAdd={mockOnAdd}
              onRemove={mockOnRemove}
              onPreview={mockOnPreview}
            />
          );

          const addButton = container.querySelector('[aria-label="添加图片"]');
          const imageCount = uniqueImages.length;

          // Property: Add button visible iff count < 9
          if (imageCount < 9) {
            expect(addButton).toBeInTheDocument();
          } else {
            expect(addButton).not.toBeInTheDocument();
          }

          // Clean up after iteration
          cleanup();

          return true;
        }
      ),
      { numRuns: 100 }
    );
  });

  /**
   * Property: Warning display based on image count
   * For any image array, warning should be visible if count >= 7, hidden if count < 7
   * 
   * **Validates: Requirements 4.9, 4.12**
   */
  it('Property: should show warning when approaching limit', () => {
    fc.assert(
      fc.property(
        fc.array(
          fc.record({
            id: fc.integer({ min: 1, max: 10000 }),
            transactionId: fc.constant(1),
            filePath: fc.string(),
            fileName: fc.string(),
            fileSize: fc.integer({ min: 1, max: 10485760 }),
            mimeType: fc.constantFrom('image/jpeg', 'image/png', 'image/heic'),
            createdAt: fc.constant('2024-01-01T00:00:00Z'),
          }),
          { minLength: 0, maxLength: 9 }
        ),
        (images) => {
          // Clean up before each iteration
          cleanup();

          // Ensure unique IDs
          const uniqueImages = images.map((img, idx) => ({
            ...img,
            id: idx + 1,
          }));

          const mockOnAdd = vi.fn();
          const mockOnRemove = vi.fn();
          const mockOnPreview = vi.fn();

          const { container } = render(
            <ImageAttachment
              images={uniqueImages}
              onAdd={mockOnAdd}
              onRemove={mockOnRemove}
              onPreview={mockOnPreview}
            />
          );

          const warning = container.querySelector('.image-attachment__warning');
          const imageCount = uniqueImages.length;

          // Property: Warning visible iff count >= 7
          if (imageCount >= 7) {
            expect(warning).toBeInTheDocument();
          } else {
            expect(warning).not.toBeInTheDocument();
          }

          // Clean up after iteration
          cleanup();

          return true;
        }
      ),
      { numRuns: 100 }
    );
  });

  /**
   * Property: Delete button count matches image count
   * For any non-empty image array (when not disabled), 
   * the number of delete buttons should equal the number of images
   * 
   * **Validates: Requirements 4.7**
   */
  it('Property: should render delete button for each image', () => {
    fc.assert(
      fc.property(
        fc.array(
          fc.record({
            id: fc.integer({ min: 1, max: 10000 }),
            transactionId: fc.constant(1),
            filePath: fc.string(),
            fileName: fc.string(),
            fileSize: fc.integer({ min: 1, max: 10485760 }),
            mimeType: fc.constantFrom('image/jpeg', 'image/png', 'image/heic'),
            createdAt: fc.constant('2024-01-01T00:00:00Z'),
          }),
          { minLength: 1, maxLength: 9 }
        ),
        (images) => {
          // Clean up before each iteration
          cleanup();

          // Ensure unique IDs
          const uniqueImages = images.map((img, idx) => ({
            ...img,
            id: idx + 1,
          }));

          const mockOnAdd = vi.fn();
          const mockOnRemove = vi.fn();
          const mockOnPreview = vi.fn();

          const { container } = render(
            <ImageAttachment
              images={uniqueImages}
              onAdd={mockOnAdd}
              onRemove={mockOnRemove}
              onPreview={mockOnPreview}
            />
          );

          const deleteButtons = container.querySelectorAll('[aria-label="删除图片"]');
          
          // Property: Number of delete buttons equals number of images
          expect(deleteButtons).toHaveLength(uniqueImages.length);

          // Clean up after iteration
          cleanup();

          return true;
        }
      ),
      { numRuns: 100 }
    );
  });

  /**
   * Property: Preview callback receives correct index
   * For any image array and any click on an image,
   * the onPreview callback should be called with the correct index
   * 
   * **Validates: Requirements 4.6**
   */
  it('Property: should call onPreview with correct index', () => {
    fc.assert(
      fc.property(
        fc.array(
          fc.record({
            id: fc.integer({ min: 1, max: 10000 }),
            transactionId: fc.constant(1),
            filePath: fc.string(),
            fileName: fc.string(),
            fileSize: fc.integer({ min: 1, max: 10485760 }),
            mimeType: fc.constantFrom('image/jpeg', 'image/png', 'image/heic'),
            createdAt: fc.constant('2024-01-01T00:00:00Z'),
          }),
          { minLength: 1, maxLength: 9 }
        ),
        fc.nat(),
        (images, clickIndexRaw) => {
          // Clean up before each iteration
          cleanup();

          // Ensure unique IDs
          const uniqueImages = images.map((img, idx) => ({
            ...img,
            id: idx + 1,
          }));

          const clickIndex = clickIndexRaw % uniqueImages.length;

          let previewedIndex: number | null = null;
          const mockOnPreview = (index: number) => {
            previewedIndex = index;
          };

          const mockOnAdd = vi.fn();
          const mockOnRemove = vi.fn();

          const { container } = render(
            <ImageAttachment
              images={uniqueImages}
              onAdd={mockOnAdd}
              onRemove={mockOnRemove}
              onPreview={mockOnPreview}
            />
          );

          // Click on the image at clickIndex
          const imageItems = container.querySelectorAll('.image-attachment__item');
          fireEvent.click(imageItems[clickIndex]);

          // Property: onPreview called with correct index
          expect(previewedIndex).toBe(clickIndex);

          // Clean up after iteration
          cleanup();

          return true;
        }
      ),
      { numRuns: 100 }
    );
  });
});
