/**
 * @jest-environment jsdom
 * @jest-environment-options {"url": "https://salsify-ecdn.com/sdk/client-id/lang-code/BTF/id-type/existing-product/index.html"}
 */

import SdkApi from '../api'
import { MessageChannel as WorkerThreadsMessageChannel } from 'worker_threads'
import { SDK_VERSION } from '../version'

const uuidRegex = /^[0-9a-f]{8}\b-[0-9a-f]{4}\b-[0-9a-f]{4}\b-[0-9a-f]{4}\b-[0-9a-f]{12}$/

const defaultOptions = {
  clientId: 'client-id',
}

describe('SdkApi', () => {
  let sdk: SdkApi

  beforeEach(() => {
    sdk = new SdkApi('npm')
  })

  describe('SDK context communication', () => {
    let contextReceived: (value?: unknown) => void
    const waitForContext = (): Promise<unknown> => new Promise(resolve => (contextReceived = resolve))
    let messageListener: ((event: MessageEvent) => void) | undefined
    let channel: MessageChannel | undefined

    // workaround for https://github.com/jsdom/jsdom/issues/2745
    const messageMonkeyPatch = (event: MessageEvent): void => {
      if (event.origin === '' && event.data.messageType === 'contextRequest') {
        event.stopImmediatePropagation()
        const eventWithOriginInitDict: MessageEventInit = {
          data: event.data,
          origin: 'https://salsify-ecdn.com',
          source: window,
        }
        if (channel) {
          eventWithOriginInitDict.ports = [channel.port2]
        }
        const eventWithOrigin: MessageEvent = new MessageEvent('message', eventWithOriginInitDict)
        window.dispatchEvent(eventWithOrigin)
      }
    }

    beforeAll(() => {
      window.addEventListener('message', messageMonkeyPatch)
    })

    afterAll(() => {
      window.removeEventListener('message', messageMonkeyPatch)
    })

    beforeEach(() => {
      messageListener = jest.fn().mockImplementation((event: MessageEvent) => {
        if (event.data.clientId) {
          contextReceived()
        }
      })
      sdk.init(defaultOptions)
    })

    afterEach(() => {
      messageListener = undefined
    })

    test('Using MessageChannel', async () => {
      channel = new WorkerThreadsMessageChannel() as unknown as MessageChannel
      channel!.port1.onmessage = messageListener!
      window.postMessage({ messageType: 'contextRequest' }, '*', [channel!.port2])
      await waitForContext()

      expect(messageListener).toHaveBeenCalledTimes(1)
      expect(messageListener).toHaveBeenCalledWith(
        expect.objectContaining({
          data: {
            url: 'https://salsify-ecdn.com/sdk/client-id/lang-code/BTF/id-type/existing-product/index.html',
            sessionId: expect.stringMatching(uuidRegex),
            pageSessionId: expect.stringMatching(uuidRegex),
            tracking: true,
            clientId: defaultOptions.clientId,
            languageCode: 'en-US',
            enhancedContent: {
              idType: 'SDKID',
            },
            version: SDK_VERSION,
            jsSource: 'npm',
          },
        })
      )

      channel!.port1.onmessage = null
      channel = undefined
    })

    test('Using postMessage', async () => {
      window.addEventListener('message', messageListener!)
      window.postMessage({ messageType: 'contextRequest' }, '*')
      await waitForContext()

      expect(messageListener).toHaveBeenCalledTimes(2)
      expect(messageListener).toHaveBeenNthCalledWith(
        1,
        expect.objectContaining({
          data: {
            messageType: 'contextRequest',
          },
        })
      )
      expect(messageListener).toHaveBeenNthCalledWith(
        2,
        expect.objectContaining({
          data: {
            url: 'https://salsify-ecdn.com/sdk/client-id/lang-code/BTF/id-type/existing-product/index.html',
            sessionId: expect.stringMatching(uuidRegex),
            pageSessionId: expect.stringMatching(uuidRegex),
            tracking: true,
            clientId: defaultOptions.clientId,
            languageCode: 'en-US',
            enhancedContent: {
              idType: 'SDKID',
            },
            version: SDK_VERSION,
            jsSource: 'npm',
          },
        })
      )

      window.removeEventListener('message', messageListener!)
    })
  })
})
