/**
 * Tests for ChannelConnection
 *
 * These tests verify the ChannelConnection class API.
 * Unit tests for class structure don't require a real backend.
 * Integration tests for actual WebSocket connections require the backend.
 *
 * Backend must be running for integration tests: docker-compose up
 */

import { ChannelConnection, RPCError } from '../connection'
import { describeIntegration, WS_URL } from '../../testing'

describe('ChannelConnection (unit tests)', () => {
    describe('construction', () => {
        it('should start in disconnected state', () => {
            const connection = new ChannelConnection({ url: 'ws://localhost/ws/' })
            expect(connection.status).toBe('disconnected')
        })

    })

    describe('status change handlers', () => {
        it('should allow subscribing to status changes', () => {
            const connection = new ChannelConnection({ url: 'ws://localhost/ws/' })
            const handler = jest.fn()

            const unsubscribe = connection.onStatusChange(handler)

            expect(typeof unsubscribe).toBe('function')
        })
    })

    describe('message handlers', () => {
        it('should allow subscribing to messages', () => {
            const connection = new ChannelConnection({ url: 'ws://localhost/ws/' })
            const handler = jest.fn()

            const unsubscribe = connection.onMessage(handler)

            expect(typeof unsubscribe).toBe('function')
        })
    })

    describe('send queueing', () => {
        it('should queue messages when not connected', () => {
            const connection = new ChannelConnection({
                url: 'ws://localhost/ws/',
                reconnect: false,
            })

            // This shouldn't throw
            connection.send({
                action: 'subscribe',
                channel: 'test',
                params: {},
            })

            // Status should still be disconnected (or connecting if it auto-connected)
            expect(['disconnected', 'connecting']).toContain(connection.status)
        })
    })

    describe('rpc', () => {
        it('should queue rpc messages when not connected', () => {
            const connection = new ChannelConnection({
                url: 'ws://localhost/ws/',
                reconnect: false,
            })

            const promise = connection.rpc('test_fn', { arg: 'value' })

            expect(promise).toBeInstanceOf(Promise)
        })
    })
})

describeIntegration('ChannelConnection (integration)', () => {
    describe('real WebSocket connection', () => {
        it('should connect to real backend WebSocket', async () => {
            const connection = new ChannelConnection({
                url: WS_URL,
                reconnect: false,
            })

            const statusChanges: string[] = []
            connection.onStatusChange((status) => {
                statusChanges.push(status)
            })

            // Connect
            connection.connect()

            // Wait for connection
            await new Promise<void>((resolve, reject) => {
                const timeout = setTimeout(() => {
                    reject(new Error('Connection timeout'))
                }, 5000)

                const unsubscribe = connection.onStatusChange((status) => {
                    if (status === 'connected') {
                        clearTimeout(timeout)
                        unsubscribe()
                        resolve()
                    }
                })
            })

            expect(connection.status).toBe('connected')

            // Cleanup
            connection.disconnect()
        })

        it('should disconnect cleanly', async () => {
            const connection = new ChannelConnection({
                url: WS_URL,
                reconnect: false,
            })

            // Connect first
            connection.connect()
            await new Promise<void>((resolve) => {
                const unsubscribe = connection.onStatusChange((status) => {
                    if (status === 'connected') {
                        unsubscribe()
                        resolve()
                    }
                })
            })

            // Now disconnect
            connection.disconnect()

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

describe('RPCError', () => {
    it('should be an Error subclass', () => {
        const error = new RPCError('TEST_CODE', 'Test message')

        expect(error).toBeInstanceOf(Error)
        expect(error).toBeInstanceOf(RPCError)
    })

    it('should have correct properties', () => {
        const error = new RPCError('VALIDATION_ERROR', 'Field is required', { field: 'email' })

        expect(error.code).toBe('VALIDATION_ERROR')
        expect(error.message).toBe('Field is required')
        expect(error.details).toEqual({ field: 'email' })
        expect(error.name).toBe('RPCError')
    })

    it('should work without details', () => {
        const error = new RPCError('NOT_FOUND', 'Function not found')

        expect(error.code).toBe('NOT_FOUND')
        expect(error.message).toBe('Function not found')
        expect(error.details).toBeUndefined()
    })
})
