/**
 * Tests for ChannelProvider context
 *
 * Unit tests run without backend.
 * Integration tests require: docker-compose up
 *
 * Run integration tests with: RUN_INTEGRATION_TESTS=true npm run test
 */

import { renderHook, act, waitFor } from '@testing-library/react'
import { ReactNode } from 'react'
import { ChannelProvider, useChannelContext, useChannelStatus } from '../context'
import { ChannelConnection } from '../connection'
import { describeIntegration, WS_URL } from '../../testing'

// ============================================================================
// Unit Tests (no backend required)
// ============================================================================

describe('ChannelProvider (unit)', () => {
    describe('useChannelContext', () => {
        it('should throw when used outside ChannelProvider', () => {
            const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {})

            expect(() => {
                renderHook(() => useChannelContext())
            }).toThrow('useChannelContext must be used within a ChannelProvider')

            consoleSpy.mockRestore()
        })

        it('should return connection and status when inside provider', () => {
            const connection = new ChannelConnection({
                url: 'ws://localhost/ws/',
                reconnect: false,
            })

            const wrapper = ({ children }: { children: ReactNode }) => (
                <ChannelProvider connection={connection} autoConnect={false}>
                    {children}
                </ChannelProvider>
            )

            const { result } = renderHook(() => useChannelContext(), { wrapper })

            expect(result.current.connection).toBe(connection)
            expect(result.current.status).toBe('disconnected')

            connection.disconnect()
        })
    })

    describe('useChannelStatus', () => {
        it('should return disconnected when autoConnect is false', () => {
            const connection = new ChannelConnection({
                url: 'ws://localhost/ws/',
                reconnect: false,
            })

            const wrapper = ({ children }: { children: ReactNode }) => (
                <ChannelProvider connection={connection} autoConnect={false}>
                    {children}
                </ChannelProvider>
            )

            const { result } = renderHook(() => useChannelStatus(), { wrapper })
            expect(result.current).toBe('disconnected')

            connection.disconnect()
        })
    })
})

// ============================================================================
// Integration Tests (require running backend)
// ============================================================================

describeIntegration('ChannelProvider (integration)', () => {
    describe('with real WebSocket connection', () => {
        let connection: ChannelConnection

        beforeEach(() => {
            connection = new ChannelConnection({
                url: WS_URL,
                reconnect: false,
            })
        })

        afterEach(() => {
            connection.disconnect()
        })

        const createWrapper = (autoConnect = true) => {
            return function Wrapper({ children }: { children: ReactNode }) {
                return (
                    <ChannelProvider
                        connection={connection}
                        autoConnect={autoConnect}
                    >
                        {children}
                    </ChannelProvider>
                )
            }
        }

        it('should auto-connect when autoConnect is true', async () => {
            const { result } = renderHook(() => useChannelContext(), {
                wrapper: createWrapper(true),
            })

            // Wait for connection
            await waitFor(() => {
                expect(result.current.status).toBe('connected')
            }, { timeout: 5000 })
        })

        it('should not auto-connect when autoConnect is false', () => {
            const { result } = renderHook(() => useChannelContext(), {
                wrapper: createWrapper(false),
            })

            expect(result.current.status).toBe('disconnected')
        })

        it('should update status when connection status changes', async () => {
            const { result } = renderHook(() => useChannelStatus(), {
                wrapper: createWrapper(true),
            })

            // Should start connecting then become connected
            await waitFor(() => {
                expect(result.current).toBe('connected')
            }, { timeout: 5000 })
        })

        it('should disconnect on unmount', async () => {
            const { result, unmount } = renderHook(() => useChannelContext(), {
                wrapper: createWrapper(true),
            })

            // Wait for connection
            await waitFor(() => {
                expect(result.current.status).toBe('connected')
            }, { timeout: 5000 })

            // Unmount
            unmount()

            // Connection should be disconnected
            expect(connection.status).toBe('disconnected')
        })
    })
})

describeIntegration('useChannelStatus (integration)', () => {
    let connection: ChannelConnection

    beforeEach(() => {
        connection = new ChannelConnection({
            url: WS_URL,
            reconnect: false,
        })
    })

    afterEach(() => {
        connection.disconnect()
    })

    const createWrapper = (autoConnect: boolean) => {
        return function Wrapper({ children }: { children: ReactNode }) {
            return (
                <ChannelProvider connection={connection} autoConnect={autoConnect}>
                    {children}
                </ChannelProvider>
            )
        }
    }

    it('should return current connection status', () => {
        const { result } = renderHook(() => useChannelStatus(), {
            wrapper: createWrapper(false),
        })

        expect(result.current).toBe('disconnected')
    })

    it('should track status through connection lifecycle', async () => {
        const { result } = renderHook(() => useChannelStatus(), {
            wrapper: createWrapper(true),
        })

        // Wait for connected
        await waitFor(() => {
            expect(result.current).toBe('connected')
        }, { timeout: 5000 })

        // Disconnect manually
        act(() => {
            connection.disconnect()
        })

        // Should become disconnected
        await waitFor(() => {
            expect(result.current).toBe('disconnected')
        })
    })
})
