/**
 * @jest-environment jsdom
 */

/*
 * Runtime Web – Unified Serverless Framework for Full-Stack TypeScript Applications
 * Documentation: https://runtimeweb.com/docs/getting-started
 *
 * @template: ai-chat
 */

/**
 * Property-based tests for ErrorState component.
 *
 * **Property 6: Error State and Recovery**
 * **Validates: Requirements 6.1, 6.2, 6.3, 6.5, 6.6**
 *
 * Property: For any error state, the ErrorState component SHALL display a message
 * without technical jargon (no HTTP codes, stack traces, or technical terms),
 * SHALL provide a Retry button that invokes onRetry, and SHALL provide a Dismiss
 * button that invokes onDismiss. The original user message SHALL be preserved for retry.
 *
 * @module components/chat/__tests__/ErrorState.property.test
 */

import * as React from 'react';
import * as fc from 'fast-check';
import { render, cleanup, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { ErrorState } from '../ErrorState';

afterEach(() => {
    cleanup();
    jest.clearAllMocks();
});

/**
 * Technical terms that should NOT appear in user-facing error messages.
 */
const TECHNICAL_TERMS = [
    /\b\d{3}\b/, // HTTP status codes like 500, 404, etc.
    /stack\s*trace/i,
    /exception/i,
    /null\s*pointer/i,
    /undefined/i,
    /TypeError/i,
    /ReferenceError/i,
    /SyntaxError/i,
    /ECONNREFUSED/i,
    /ETIMEDOUT/i,
    /ENOTFOUND/i,
    /\bAPI\b/,
    /endpoint/i,
    /HTTP/i,
    /JSON/i,
    /parse\s*error/i,
];

/**
 * Arbitrary generator for various error types.
 */
const errorArbitrary = (): fc.Arbitrary<Error> => {
    return fc.oneof(
        fc.constant(new Error('Network error')),
        fc.constant(new Error('Failed to fetch')),
        fc.constant(new Error('Request timeout')),
        fc.constant(new Error('500 Internal Server Error')),
        fc.constant(new Error('Server error occurred')),
        fc.constant(new Error('401 Unauthorized')),
        fc.constant(new Error('403 Forbidden')),
        fc.constant(new Error('404 Not Found')),
        fc.constant(new Error('429 Too Many Requests')),
        fc.constant(new Error('Rate limit exceeded')),
        fc.constant(new Error('Unknown error')),
        fc.constant(new Error('Something went wrong')),
        fc.string({ minLength: 1, maxLength: 100 }).map((msg) => new Error(msg)),
    );
};

describe('ErrorState Property Tests', () => {
    /**
     * **Property 6: Error State and Recovery**
     * **Validates: Requirements 6.1, 6.2, 6.3, 6.5, 6.6**
     */
    describe('Property 6: Error State and Recovery', () => {
        /**
         * Validates: Requirement 6.1
         * The ErrorState SHALL display a message without technical jargon.
         */
        it('displays user-friendly message without technical jargon', () => {
            fc.assert(
                fc.property(errorArbitrary(), (error) => {
                    const onRetry = jest.fn();
                    const onDismiss = jest.fn();
                    const { unmount } = render(
                        <ErrorState error={error} onRetry={onRetry} onDismiss={onDismiss} />
                    );

                    const messageElement = screen.getByTestId('error-state-message');
                    const displayedMessage = messageElement.textContent || '';

                    // Message should not contain technical terms
                    TECHNICAL_TERMS.forEach((pattern) => {
                        expect(displayedMessage).not.toMatch(pattern);
                    });

                    // Message should be non-empty
                    expect(displayedMessage.trim()).toBeTruthy();

                    unmount();
                }),
                { numRuns: 100 },
            );
        });

        /**
         * Validates: Requirement 6.2
         * The ErrorState SHALL provide a Retry button.
         */
        it('provides a Retry button', () => {
            fc.assert(
                fc.property(errorArbitrary(), (error) => {
                    const onRetry = jest.fn();
                    const onDismiss = jest.fn();
                    const { unmount } = render(
                        <ErrorState error={error} onRetry={onRetry} onDismiss={onDismiss} />
                    );

                    const retryButton = screen.getByTestId('error-state-retry');
                    expect(retryButton).toBeInTheDocument();
                    expect(retryButton.tagName.toLowerCase()).toBe('button');

                    unmount();
                }),
                { numRuns: 50 },
            );
        });

        /**
         * Validates: Requirement 6.2
         * Clicking Retry button SHALL invoke onRetry callback.
         */
        it('Retry button invokes onRetry callback', () => {
            fc.assert(
                fc.property(errorArbitrary(), (error) => {
                    const onRetry = jest.fn();
                    const onDismiss = jest.fn();
                    const { unmount } = render(
                        <ErrorState error={error} onRetry={onRetry} onDismiss={onDismiss} />
                    );

                    const retryButton = screen.getByTestId('error-state-retry');
                    fireEvent.click(retryButton);

                    expect(onRetry).toHaveBeenCalledTimes(1);
                    expect(onDismiss).not.toHaveBeenCalled();

                    unmount();
                }),
                { numRuns: 50 },
            );
        });

        /**
         * Validates: Requirement 6.5
         * The ErrorState SHALL provide a Dismiss button.
         */
        it('provides a Dismiss button', () => {
            fc.assert(
                fc.property(errorArbitrary(), (error) => {
                    const onRetry = jest.fn();
                    const onDismiss = jest.fn();
                    const { unmount } = render(
                        <ErrorState error={error} onRetry={onRetry} onDismiss={onDismiss} />
                    );

                    const dismissButton = screen.getByTestId('error-state-dismiss');
                    expect(dismissButton).toBeInTheDocument();
                    expect(dismissButton.tagName.toLowerCase()).toBe('button');

                    unmount();
                }),
                { numRuns: 50 },
            );
        });

        /**
         * Validates: Requirement 6.5
         * Clicking Dismiss button SHALL invoke onDismiss callback.
         */
        it('Dismiss button invokes onDismiss callback', () => {
            fc.assert(
                fc.property(errorArbitrary(), (error) => {
                    const onRetry = jest.fn();
                    const onDismiss = jest.fn();
                    const { unmount } = render(
                        <ErrorState error={error} onRetry={onRetry} onDismiss={onDismiss} />
                    );

                    const dismissButton = screen.getByTestId('error-state-dismiss');
                    fireEvent.click(dismissButton);

                    expect(onDismiss).toHaveBeenCalledTimes(1);
                    expect(onRetry).not.toHaveBeenCalled();

                    unmount();
                }),
                { numRuns: 50 },
            );
        });

        /**
         * Validates: Accessibility
         * The ErrorState SHALL have role="alert" for screen readers.
         */
        it('has role="alert" for accessibility', () => {
            fc.assert(
                fc.property(errorArbitrary(), (error) => {
                    const onRetry = jest.fn();
                    const onDismiss = jest.fn();
                    const { unmount } = render(
                        <ErrorState error={error} onRetry={onRetry} onDismiss={onDismiss} />
                    );

                    const errorState = screen.getByTestId('error-state');
                    expect(errorState).toHaveAttribute('role', 'alert');

                    unmount();
                }),
                { numRuns: 50 },
            );
        });

        /**
         * Validates: Requirement 6.3
         * The ErrorState SHALL indicate message is preserved for retry.
         */
        it('indicates message is preserved for retry', () => {
            fc.assert(
                fc.property(errorArbitrary(), (error) => {
                    const onRetry = jest.fn();
                    const onDismiss = jest.fn();
                    const { container, unmount } = render(
                        <ErrorState error={error} onRetry={onRetry} onDismiss={onDismiss} />
                    );

                    const hint = container.querySelector('.error-state__hint');
                    expect(hint).not.toBeNull();
                    expect(hint?.textContent?.toLowerCase()).toContain('saved');

                    unmount();
                }),
                { numRuns: 50 },
            );
        });

        /**
         * Validates: Consistent rendering
         * The ErrorState SHALL render consistently across multiple renders.
         */
        it('renders consistently across multiple renders', () => {
            fc.assert(
                fc.property(errorArbitrary(), fc.integer({ min: 1, max: 5 }), (error, renderCount) => {
                    const onRetry = jest.fn();
                    const onDismiss = jest.fn();

                    for (let i = 0; i < renderCount; i++) {
                        const { container, unmount } = render(
                            <ErrorState error={error} onRetry={onRetry} onDismiss={onDismiss} />
                        );

                        expect(container.querySelector('.error-state')).not.toBeNull();
                        expect(container.querySelector('.error-state__message')).not.toBeNull();
                        expect(container.querySelector('.error-state__retry')).not.toBeNull();
                        expect(container.querySelector('.error-state__dismiss')).not.toBeNull();

                        unmount();
                    }
                }),
                { numRuns: 20 },
            );
        });
    });
});
