import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { EventEmitter } from 'events'

// Mock parziale di una WebSocket e del Server, analogo a websocket.test.js ma
// con send/terminate che possono essere forzati a lanciare per esercitare i
// rami di gestione errori altrimenti irraggiungibili.
class MockWebSocket extends EventEmitter {
    constructor() {
        super()
        this.readyState = 1 // OPEN
    }
    send = vi.fn()
    terminate = vi.fn()
}

class MockWebSocketServer extends EventEmitter {
    clients = new Set()
}

describe('websocket-handler.js — rami di errore', () => {
    afterEach(() => {
        vi.restoreAllMocks()
        vi.resetModules()
        vi.doUnmock('../../src/gameState.js')
    })

    describe('errori di invio (ws.send che lancia)', () => {
        let wss, setupWebSocketHandler, consoleSpy

        beforeEach(async () => {
            vi.resetModules()
            ;({ setupWebSocketHandler } = await import('../../src/websocket-handler.js'))
            wss = new MockWebSocketServer()
            consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
        })

        it('handleRegister: se ws.send lancia durante l\'invio dello stato iniziale, non deve propagare l\'errore', () => {
            setupWebSocketHandler(wss)
            const ws = new MockWebSocket()
            ws.send.mockImplementation(() => { throw new Error('socket chiuso a metà') })
            wss.emit('connection', ws)
            wss.clients.add(ws)

            expect(() => {
                ws.emit('message', JSON.stringify({ type: 'register', role: 'display' }))
            }).not.toThrow()
            expect(consoleSpy).toHaveBeenCalledWith('Error sending initial state:', expect.any(Error))
        })

        it('sendError: se ws.send lancia rispondendo a un JSON non valido, non deve propagare l\'errore', () => {
            setupWebSocketHandler(wss)
            const ws = new MockWebSocket()
            ws.send.mockImplementation(() => { throw new Error('socket chiuso a metà') })
            wss.emit('connection', ws)
            wss.clients.add(ws)

            // JSON non valido → handleMessage va nel catch → sendError prova a rispondere → ws.send lancia
            expect(() => {
                ws.emit('message', 'non è JSON {{{')
            }).not.toThrow()
            expect(consoleSpy).toHaveBeenCalledWith('Failed to send error message:', expect.any(Error))
        })

        it('sendError: se ws.send lancia rispondendo a un\'azione invalida, non deve propagare l\'errore', () => {
            setupWebSocketHandler(wss)
            const controller = new MockWebSocket()
            wss.emit('connection', controller)
            wss.clients.add(controller)
            controller.emit('message', JSON.stringify({ type: 'register', role: 'controller' }))

            controller.send.mockImplementation(() => { throw new Error('socket chiuso a metà') })
            expect(() => {
                controller.emit('message', JSON.stringify({ type: 'action' }))
            }).not.toThrow()
            expect(consoleSpy).toHaveBeenCalledWith('Failed to send error message:', expect.any(Error))
        })
    })

    describe('handleError: ws.terminate che lancia', () => {
        it('non deve propagare l\'errore se la chiusura forzata fallisce', async () => {
            vi.resetModules()
            const { setupWebSocketHandler } = await import('../../src/websocket-handler.js')
            const wss = new MockWebSocketServer()
            setupWebSocketHandler(wss)
            const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})

            const ws = new MockWebSocket()
            ws.terminate.mockImplementation(() => { throw new Error('già chiusa') })
            wss.emit('connection', ws)

            const err = new Error('Invalid UTF8')
            err.code = 'WS_ERR_INVALID_UTF8'
            expect(() => ws.emit('error', err)).not.toThrow()
            expect(consoleSpy).toHaveBeenCalledWith('Error closing connection:', expect.any(Error))
        })
    })

    describe('handleAction: applyAction che lancia', () => {
        it('non deve propagare l\'errore, ripristina lo stato precedente e notifica il client', async () => {
            vi.resetModules()
            vi.doMock('../../src/gameState.js', async (importOriginal) => {
                const actual = await importOriginal()
                return {
                    ...actual,
                    applyAction: vi.fn((state, action) => {
                        if (action.type === 'boom') throw new Error('regola di gioco rotta')
                        return actual.applyAction(state, action)
                    }),
                }
            })
            const { setupWebSocketHandler } = await import('../../src/websocket-handler.js')
            const wss = new MockWebSocketServer()
            const handler = setupWebSocketHandler(wss)
            const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})

            const controller = new MockWebSocket()
            wss.emit('connection', controller)
            wss.clients.add(controller)
            controller.emit('message', JSON.stringify({ type: 'register', role: 'controller' }))
            const statoPrecedente = handler.getState()
            controller.send.mockClear()

            expect(() => {
                controller.emit('message', JSON.stringify({ type: 'action', action: { type: 'boom' } }))
            }).not.toThrow()

            expect(consoleSpy).toHaveBeenCalledWith('Error applying action:', expect.any(Error))
            expect(handler.getState()).toEqual(statoPrecedente)
            const msg = JSON.parse(controller.send.mock.calls.at(-1)[0])
            expect(msg.type).toBe('error')
            expect(msg.message).toContain('Failed to apply action')
        })
    })
})
