/**
 * @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 Accessibility Compliance.
 *
 * **Property 9: Accessibility Compliance**
 * **Validates: Requirements 10.2, 10.3, 10.4, 10.5, 10.7**
 *
 * Property: All interactive elements SHALL have proper ARIA attributes,
 * keyboard accessibility, and screen reader support.
 *
 * @module components/chat/__tests__/Accessibility.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 { TypingIndicator } from '../TypingIndicator';
import { EmptyState } from '../EmptyState';
import { ErrorState } from '../ErrorState';
import { InputComposer } from '../InputComposer';
import { SessionIndicator } from '../SessionIndicator';
import { MessageList } from '../MessageList';
import type { Message } from '../../../types';

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

describe('Accessibility Property Tests', () => {
    /**
     * **Property 9: Accessibility Compliance**
     * **Validates: Requirements 10.2, 10.3, 10.4, 10.5, 10.7**
     */
    describe('Property 9: Accessibility Compliance', () => {
        /**
         * Validates: Requirement 10.2
         * All interactive elements SHALL be keyboard accessible.
         */
        describe('Keyboard Accessibility', () => {
            it('EmptyState prompt buttons are keyboard accessible', () => {
                fc.assert(
                    fc.property(fc.integer({ min: 0, max: 3 }), (promptIndex) => {
                        const onPromptSelect = jest.fn();
                        const { unmount } = render(<EmptyState onPromptSelect={onPromptSelect} />);

                        const promptButton = screen.getByTestId(`empty-state-prompt-${promptIndex}`);

                        // Should be focusable
                        promptButton.focus();
                        expect(document.activeElement).toBe(promptButton);

                        // Should respond to Enter key
                        fireEvent.keyDown(promptButton, { key: 'Enter' });
                        fireEvent.keyUp(promptButton, { key: 'Enter' });
                        fireEvent.click(promptButton);
                        expect(onPromptSelect).toHaveBeenCalled();

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

            it('ErrorState buttons are keyboard accessible', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const onRetry = jest.fn();
                        const onDismiss = jest.fn();
                        const error = new Error('Test error');
                        const { unmount } = render(
                            <ErrorState error={error} onRetry={onRetry} onDismiss={onDismiss} />
                        );

                        const retryButton = screen.getByTestId('error-state-retry');
                        const dismissButton = screen.getByTestId('error-state-dismiss');

                        // Retry button should be focusable
                        retryButton.focus();
                        expect(document.activeElement).toBe(retryButton);

                        // Dismiss button should be focusable
                        dismissButton.focus();
                        expect(document.activeElement).toBe(dismissButton);

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

            it('InputComposer textarea and send button are keyboard accessible', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const onSend = jest.fn();
                        const { unmount } = render(
                            <InputComposer onSend={onSend} disabled={false} />
                        );

                        const textarea = screen.getByTestId('input-composer-textarea');
                        const sendButton = screen.getByTestId('input-composer-send');

                        // Textarea should be focusable
                        textarea.focus();
                        expect(document.activeElement).toBe(textarea);

                        // Send button should be focusable (when enabled)
                        fireEvent.change(textarea, { target: { value: 'test' } });
                        sendButton.focus();
                        expect(document.activeElement).toBe(sendButton);

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

            it('SessionIndicator button is keyboard accessible', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const onNewSession = jest.fn();
                        const { unmount } = render(
                            <SessionIndicator
                                sessionId="test-session"
                                messageCount={5}
                                onNewSession={onNewSession}
                            />
                        );

                        const button = screen.getByTestId('session-indicator-button');

                        // Should be focusable
                        button.focus();
                        expect(document.activeElement).toBe(button);

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

        /**
         * Validates: Requirement 10.4
         * Dynamic content SHALL use aria-live regions.
         */
        describe('ARIA Live Regions', () => {
            it('TypingIndicator has aria-live region', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const { container, unmount } = render(<TypingIndicator />);

                        const indicator = container.querySelector('.typing-indicator');
                        expect(indicator).toHaveAttribute('aria-live', 'polite');
                        expect(indicator).toHaveAttribute('role', 'status');

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

            it('ErrorState has alert role for immediate announcement', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const error = new Error('Test error');
                        const { container, unmount } = render(
                            <ErrorState error={error} onRetry={jest.fn()} onDismiss={jest.fn()} />
                        );

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

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

            it('MessageList has aria-live region for new messages', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const messages: Message[] = [
                            { id: '1', role: 'user', content: 'Hello', timestamp: new Date().toISOString() },
                        ];
                        const { container, unmount } = render(
                            <MessageList messages={messages} isLoading={false} />
                        );

                        const messageList = container.querySelector('.message-list');
                        expect(messageList).toHaveAttribute('aria-live', 'polite');
                        expect(messageList).toHaveAttribute('role', 'log');

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

        /**
         * Validates: Requirement 10.5
         * All interactive elements SHALL have accessible labels.
         */
        describe('Accessible Labels', () => {
            it('TypingIndicator has aria-label', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const { container, unmount } = render(<TypingIndicator />);

                        const indicator = container.querySelector('.typing-indicator');
                        expect(indicator).toHaveAttribute('aria-label');
                        expect(indicator?.getAttribute('aria-label')).toBeTruthy();

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

            it('InputComposer textarea has aria-label', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const { unmount } = render(
                            <InputComposer onSend={jest.fn()} disabled={false} />
                        );

                        const textarea = screen.getByTestId('input-composer-textarea');
                        expect(textarea).toHaveAttribute('aria-label');

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

            it('InputComposer send button has aria-label', () => {
                fc.assert(
                    fc.property(fc.boolean(), (disabled) => {
                        const { unmount } = render(
                            <InputComposer onSend={jest.fn()} disabled={disabled} />
                        );

                        const sendButton = screen.getByTestId('input-composer-send');
                        expect(sendButton).toHaveAttribute('aria-label');

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

            it('ErrorState dismiss button has aria-label', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const error = new Error('Test error');
                        const { unmount } = render(
                            <ErrorState error={error} onRetry={jest.fn()} onDismiss={jest.fn()} />
                        );

                        const dismissButton = screen.getByTestId('error-state-dismiss');
                        expect(dismissButton).toHaveAttribute('aria-label');

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

            it('EmptyState prompts container has aria-label', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const { container, unmount } = render(
                            <EmptyState onPromptSelect={jest.fn()} />
                        );

                        const promptsContainer = container.querySelector('.empty-state__prompts');
                        expect(promptsContainer).toHaveAttribute('aria-label');

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

            it('MessageList has aria-label', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const messages: Message[] = [
                            { id: '1', role: 'user', content: 'Hello', timestamp: new Date().toISOString() },
                        ];
                        const { container, unmount } = render(
                            <MessageList messages={messages} isLoading={false} />
                        );

                        const messageList = container.querySelector('.message-list');
                        expect(messageList).toHaveAttribute('aria-label');

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

        /**
         * Validates: Requirement 10.7
         * Decorative elements SHALL be hidden from screen readers.
         */
        describe('Decorative Elements Hidden', () => {
            it('TypingIndicator dots are hidden from screen readers', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const { container, unmount } = render(<TypingIndicator />);

                        const dots = container.querySelector('.typing-indicator__dots');
                        expect(dots).toHaveAttribute('aria-hidden', 'true');

                        const avatar = container.querySelector('.typing-indicator__avatar-icon');
                        expect(avatar).toHaveAttribute('aria-hidden', 'true');

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

            it('EmptyState icon is hidden from screen readers', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const { container, unmount } = render(
                            <EmptyState onPromptSelect={jest.fn()} />
                        );

                        const icon = container.querySelector('.empty-state__icon');
                        expect(icon).toHaveAttribute('aria-hidden', 'true');

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

            it('EmptyState prompt icons are hidden from screen readers', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const { container, unmount } = render(
                            <EmptyState onPromptSelect={jest.fn()} />
                        );

                        const promptIcons = container.querySelectorAll('.empty-state__prompt-icon');
                        promptIcons.forEach((icon) => {
                            expect(icon).toHaveAttribute('aria-hidden', 'true');
                        });

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

            it('ErrorState icon is hidden from screen readers', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const error = new Error('Test error');
                        const { container, unmount } = render(
                            <ErrorState error={error} onRetry={jest.fn()} onDismiss={jest.fn()} />
                        );

                        const icon = container.querySelector('.error-state__icon');
                        expect(icon).toHaveAttribute('aria-hidden', 'true');

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

            it('SessionIndicator dot is hidden from screen readers', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const { container, unmount } = render(
                            <SessionIndicator
                                sessionId="test-session"
                                messageCount={5}
                                onNewSession={jest.fn()}
                            />
                        );

                        const dot = container.querySelector('.session-indicator__dot');
                        expect(dot).toHaveAttribute('aria-hidden', 'true');

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

        /**
         * Validates: Requirement 10.3
         * Focus management SHALL be logical and predictable.
         */
        describe('Focus Management', () => {
            it('SessionIndicator menu has proper ARIA attributes when expanded', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const onNewSession = jest.fn();
                        const { unmount } = render(
                            <SessionIndicator
                                sessionId="test-session"
                                messageCount={5}
                                onNewSession={onNewSession}
                            />
                        );

                        const button = screen.getByTestId('session-indicator-button');

                        // Should have aria-haspopup
                        expect(button).toHaveAttribute('aria-haspopup', 'true');

                        // Initially collapsed
                        expect(button).toHaveAttribute('aria-expanded', 'false');

                        // Click to expand
                        fireEvent.click(button);
                        expect(button).toHaveAttribute('aria-expanded', 'true');

                        // Menu should have role="menu"
                        const menu = screen.getByTestId('session-indicator-menu');
                        expect(menu).toHaveAttribute('role', 'menu');

                        // Menu item should have role="menuitem"
                        const menuItem = screen.getByTestId('session-indicator-new-session');
                        expect(menuItem).toHaveAttribute('role', 'menuitem');

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

            it('InputComposer has aria-describedby for keyboard hints', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const { container, unmount } = render(
                            <InputComposer onSend={jest.fn()} disabled={false} />
                        );

                        const textarea = screen.getByTestId('input-composer-textarea');
                        const describedById = textarea.getAttribute('aria-describedby');
                        expect(describedById).toBeTruthy();

                        // The described element should exist
                        const hintElement = container.querySelector(`#${describedById}`);
                        expect(hintElement).not.toBeNull();

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

        /**
         * Validates: All buttons have type="button"
         * Prevents accidental form submission.
         */
        describe('Button Types', () => {
            it('all buttons have explicit type attribute', () => {
                fc.assert(
                    fc.property(fc.constant(true), () => {
                        const error = new Error('Test error');

                        // Render all components with buttons
                        const { container: emptyContainer, unmount: unmountEmpty } = render(
                            <EmptyState onPromptSelect={jest.fn()} />
                        );
                        const emptyButtons = emptyContainer.querySelectorAll('button');
                        emptyButtons.forEach((btn) => {
                            expect(btn).toHaveAttribute('type', 'button');
                        });
                        unmountEmpty();

                        const { container: errorContainer, unmount: unmountError } = render(
                            <ErrorState error={error} onRetry={jest.fn()} onDismiss={jest.fn()} />
                        );
                        const errorButtons = errorContainer.querySelectorAll('button');
                        errorButtons.forEach((btn) => {
                            expect(btn).toHaveAttribute('type', 'button');
                        });
                        unmountError();

                        const { container: inputContainer, unmount: unmountInput } = render(
                            <InputComposer onSend={jest.fn()} disabled={false} />
                        );
                        const inputButtons = inputContainer.querySelectorAll('button');
                        inputButtons.forEach((btn) => {
                            expect(btn).toHaveAttribute('type', 'button');
                        });
                        unmountInput();

                        const { container: sessionContainer, unmount: unmountSession } = render(
                            <SessionIndicator
                                sessionId="test-session"
                                messageCount={5}
                                onNewSession={jest.fn()}
                            />
                        );
                        const sessionButtons = sessionContainer.querySelectorAll('button');
                        sessionButtons.forEach((btn) => {
                            expect(btn).toHaveAttribute('type', 'button');
                        });
                        unmountSession();
                    }),
                    { numRuns: 5 },
                );
            });
        });
    });
});
