import { createSocketMethods } from '../socket'
import { io, Socket } from 'socket.io-client'

// Mock socket.io-client
jest.mock('socket.io-client', () => {
    const mockSocket = {
        connected: false,
        connect: jest.fn(),
        disconnect: jest.fn(),
        on: jest.fn(),
        off: jest.fn(),
        emit: jest.fn(),
    }
    return {
        io: jest.fn().mockReturnValue(mockSocket),
        Socket: jest.fn(),
    }
})

describe('createSocketMethods', () => {
    let socketMethods: ReturnType<typeof createSocketMethods>
    const mockSocket = (io as jest.Mock)()

    beforeEach(() => {
        jest.clearAllMocks()
        socketMethods = createSocketMethods()

        // Reset the connected state
        Object.defineProperty(mockSocket, 'connected', {
            value: false,
            writable: true,
        })

        // Set up a global window.location.origin for testing
        global.window = {
            location: {
                origin: 'http://test-origin.com',
            },
        } as any
    })

    it('should create socket methods object', () => {
        expect(socketMethods).toHaveProperty('connect')
        expect(socketMethods).toHaveProperty('disconnect')
        expect(socketMethods).toHaveProperty('on')
        expect(socketMethods).toHaveProperty('off')
        expect(socketMethods).toHaveProperty('emit')
    })

    it('should connect to socket server with default URL if in browser', () => {
        socketMethods.connect()

        // Should create socket with window.location.origin
        expect(io).toHaveBeenCalledWith('http://test-origin.com')
        // Should call connect() on the socket
        expect(mockSocket.connect).toHaveBeenCalled()
    })

    it('should connect to socket server with localhost fallback if not in browser', () => {
        global.window = undefined as any
        socketMethods.connect()

        // Should use localhost fallback URL
        expect(io).toHaveBeenCalledWith('http://localhost:3000')
        expect(mockSocket.connect).toHaveBeenCalled()
    })

    it('should not try to reconnect if already connected', () => {
        // First connection
        socketMethods.connect()
        expect(io).toHaveBeenCalledTimes(1)

        // Mark as connected
        Object.defineProperty(mockSocket, 'connected', { value: true })

        // Try connecting again
        socketMethods.connect()

        // Should not create a new socket
        expect(io).toHaveBeenCalledTimes(1)
        // But should still call connect()
        expect(mockSocket.connect).toHaveBeenCalledTimes(1)
    })

    it('should disconnect from socket server', () => {
        // Connect first
        socketMethods.connect()

        // Mark as connected
        Object.defineProperty(mockSocket, 'connected', { value: true })

        // Disconnect
        socketMethods.disconnect()

        // Should call disconnect() on the socket
        expect(mockSocket.disconnect).toHaveBeenCalled()
    })

    it('should not try to disconnect if not connected', () => {
        // No connection established
        socketMethods.disconnect()

        // Should not call disconnect()
        expect(mockSocket.disconnect).not.toHaveBeenCalled()
    })

    it('should register event handler', () => {
        // Connect first
        socketMethods.connect()

        const handleEvent = jest.fn()
        socketMethods.on('test-event', handleEvent)

        // Should register the handler
        expect(mockSocket.on).toHaveBeenCalledWith('test-event', handleEvent)
    })

    it('should remove event handler', () => {
        // Connect first
        socketMethods.connect()

        const handleEvent = jest.fn()
        socketMethods.on('test-event', handleEvent)
        socketMethods.off('test-event')

        // Should remove the handler
        expect(mockSocket.off).toHaveBeenCalledWith('test-event', handleEvent)
    })

    it('should emit event with arguments', () => {
        // Connect first
        socketMethods.connect()

        const result = socketMethods.emit('test-event', 'arg1', { foo: 'bar' })

        // Should emit the event with arguments
        expect(mockSocket.emit).toHaveBeenCalledWith('test-event', 'arg1', {
            foo: 'bar',
        })
        // Should return true indicating success
        expect(result).toBe(true)
    })

    it('should return false when emitting without connection', () => {
        // No connection established
        const result = socketMethods.emit('test-event', 'arg1')

        // Should not emit
        expect(mockSocket.emit).not.toHaveBeenCalled()
        // Should return false indicating failure
        expect(result).toBe(false)
    })
})
