import { describe, test, it, expect } from 'vitest'
import SseSource from "~/services/sse/sseSource";
import {EventSourcePolyfill} from "event-source-polyfill";

class TestableSseSource extends SseSource {
    public getUrl() { return this.url }
    public getOnOpen() { return this.onOpen }
    public getOnMessage() { return this.onMessage }
    public getOnClose() { return this.onClose }
    public getWithCredentials() { return this.withCredentials }

    public createEventSource(url: string, withCredentials: boolean): EventSourcePolyfill {
        return super.createEventSource(url, withCredentials)
    }

    public setEventSource(eventSource: EventSourcePolyfill) {
        this.eventSource = eventSource
    }
}

let mercureUrl: string
let topic: string
let onOpen: () => any
let onMessage: (data: Array<any>) => any
let onClose: () => any
let sseSource: TestableSseSource

const init_console_log = console.log

beforeEach(() => {
    mercureUrl = 'https://my.mercure.com'
    topic = 'mytopic'

    onOpen = () => 'opened'
    onMessage = (data: Array<any>) => 'message'
    onClose = () => 'closed'

    sseSource = new TestableSseSource(mercureUrl, topic, onOpen, onMessage, onClose, false)
})

afterEach(() => {
    console.log = init_console_log
    vi.restoreAllMocks()
})

describe('test constructor', () => {
    test('with all params', () => {
        expect(sseSource.getUrl().toString()).toEqual('https://my.mercure.com/?topic=mytopic')
        expect(sseSource.getOnOpen()).toEqual(onOpen)
        expect(sseSource.getOnMessage()).toEqual(onMessage)
        expect(sseSource.getOnClose()).toEqual(onClose)
        expect(sseSource.getWithCredentials()).toEqual(false)
    })
})

describe('createEventSource', () => {
    test('simple call', () => {
        const eventSource = sseSource.createEventSource(mercureUrl, false)

        expect(eventSource.readyState).toEqual(0)
        expect(eventSource.url).toEqual(mercureUrl)
        expect(eventSource.withCredentials).toEqual(false)
    })
})

describe('isConnected', () => {
    test('no event source', () => {
        expect(sseSource.isConnected()).toEqual(false)
    })
    test('got an event source, but it is not open', () => {
        const eventSource = sseSource.createEventSource(mercureUrl, true)
        sseSource.setEventSource(eventSource)
        expect(sseSource.isConnected()).toEqual(false)
    })
    test('got an open event source', () => {
        // @ts-ignore
        const eventSource = vi.fn() as EventSourcePolyfill
        // @ts-ignore
        // noinspection JSConstantReassignment
        eventSource.readyState = EventSourcePolyfill.OPEN
        sseSource.setEventSource(eventSource)
        expect(sseSource.isConnected()).toEqual(true)
    })
})

describe('subscribe', () => {
    test('already connected', () => {
        sseSource.isConnected = vi.fn(() => true)
        expect(() => sseSource.subscribe()).toThrowError("SSE - Already subscribed to this event source")
    })
    test('is server side', () => {
        process.server = true
        expect(() => sseSource.subscribe()).toThrowError("SSE - Cannot subscribe on server side")
        process.server = false
    })
    test('successful subscription', () => {

        onOpen = vi.fn(() => null)
        onMessage = vi.fn((data: Array<any>) => null)

        sseSource = new TestableSseSource(mercureUrl, topic, onOpen, onMessage, onClose)

        const dummyEventSource = new EventSourcePolyfill('https://my.mercure.com', { withCredentials: true })

        sseSource.createEventSource = vi.fn((url: string, withCredentials: boolean) => {
            return dummyEventSource
        })

        console.log = vi.fn()
        console.error = vi.fn()

        sseSource.subscribe()
        expect(sseSource.createEventSource).toHaveBeenCalled()

        // @ts-ignore
        dummyEventSource.onopen()
        expect(onOpen).toHaveBeenCalled()
        expect(console.log).toHaveBeenCalledWith('SSE - Listening for events...')

        // @ts-ignore
        dummyEventSource.onmessage({ data: '1' })
        expect(onMessage).toHaveBeenCalledWith(1)

        // @ts-ignore
        dummyEventSource.onerror()
        expect(console.error).toHaveBeenCalledWith('SSE - An error happened')
    })
})

describe('unsubscribe', () => {
    test('if no event source, does nothing', () => {
        onClose = vi.fn(() => null)
        sseSource = new TestableSseSource(mercureUrl, topic, onOpen, onMessage, onClose)

        sseSource.unsubscribe()

        expect(onClose).toHaveBeenCalledTimes(0)
    })

    test('if event source is not opened, does nothing', () => {
        onClose = vi.fn(() => null)
        sseSource = new TestableSseSource(mercureUrl, topic, onOpen, onMessage, onClose)

        // @ts-ignore
        const eventSource = vi.fn() as EventSourcePolyfill
        // @ts-ignore
        // noinspection JSConstantReassignment
        eventSource.readyState = EventSourcePolyfill.CLOSED
        sseSource.setEventSource(eventSource)

        sseSource.unsubscribe()

        expect(onClose).toHaveBeenCalledTimes(0)
    })

    test('has an open subscription', () => {
        onClose = vi.fn(() => null)
        sseSource = new TestableSseSource(mercureUrl, topic, onOpen, onMessage, onClose)

        // @ts-ignore
        const eventSource = vi.fn() as EventSourcePolyfill
        // @ts-ignore
        // noinspection JSConstantReassignment
        eventSource.readyState = EventSourcePolyfill.OPEN
        eventSource.close = vi.fn(() => null)
        sseSource.setEventSource(eventSource)

        console.log = vi.fn()

        sseSource.unsubscribe()

        expect(eventSource.close).toHaveBeenCalled()
        expect(onClose).toHaveBeenCalled()
        expect(console.log).toHaveBeenCalledWith('SSE - Subscription closed')
    })
})
