import { test } from '@japa/runner'
import sinon from 'sinon'
import { EventTypes } from '../../../src/events/EventTypes.js'
import { Events } from '../../../src/events/Events.js'
import { ensureUnique } from './EventsTestHelpers.js'
import { IHttpRequest, HttpRequest, Kind as HttpRequestKind } from '../../../src/models/HttpRequest.js'
import { Kind as HttpProjectKind } from '../../../src/models/HttpProject.js'
import { IHttpRequestDetail } from '../../../src/events/transport/TransportEvents.js'
import { IRequestProxyInit } from '../../../src/proxy/RequestProxy.js'
import { ContextEventDetailWithResult } from '../../../src/events/BaseEvents.js'
import { IHttpProjectProxyInit, IHttpProjectStoreProxyInit } from '../../../src/proxy/HttpProjectProxy.js'
import './events_polyfills.js'

test.group('Events > Transport > EventTypes.Transport.Core', () => {
  test('has "{prop}" property')
    .with([
      ['request', 'transportcorerequest'],
      ['httpProject', 'transportcorehttpproject'],
    ])
    .run(({ assert }, [prop, value]) => {
      // @ts-expect-error Used in testing
      assert.equal(EventTypes.Transport.Core[prop], value)
    })

  test('has unique events for the namespace', () => {
    ensureUnique('EventTypes.Transport.Core', EventTypes.Transport.Core)
  })
})

test.group('Events > Transport > EventTypes.Transport.Http', () => {
  test('has "{prop}" property')
    .with([['send', 'httptransportsend']])
    .run(({ assert }, [prop, value]) => {
      // @ts-expect-error Used in testing
      assert.equal(EventTypes.Transport.Http[prop], value)
    })

  test('has unique events for the namespace', () => {
    ensureUnique('EventTypes.Transport.Http', EventTypes.Transport.Http)
  })
})

test.group('Events > Transport > EventTypes.Transport.Ws', () => {
  test('has "{prop}" property')
    .with([
      ['connect', 'wstransportconnect'],
      ['disconnect', 'wstransportdisconnect'],
      ['send', 'wstransportsend'],
    ])
    .run(({ assert }, [prop, value]) => {
      // @ts-expect-error Used in testing
      assert.equal(EventTypes.Transport.Ws[prop], value)
    })

  test('has unique events for the namespace', () => {
    ensureUnique('EventTypes.Transport.Ws', EventTypes.Transport.Ws)
  })
})

test.group('Events > Transport > Events.Transport.Core > request()', () => {
  const init: IRequestProxyInit = {
    kind: HttpRequestKind,
    request: new HttpRequest().toJSON(),
  }

  test('dispatches the event', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Transport.Core.request, spy)
    await Events.Transport.Core.request(init)
    globalThis.removeEventListener(EventTypes.Transport.Core.request, spy)
    assert.isTrue(spy.calledOnce)
  })

  test('has the detail object', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Transport.Core.request, spy)
    await Events.Transport.Core.request(init)
    globalThis.removeEventListener(EventTypes.Transport.Core.request, spy)
    const e = spy.args[0][0] as CustomEvent<ContextEventDetailWithResult<IRequestProxyInit>>
    assert.deepEqual(e.detail, { ...init, result: undefined })
  })
})

test.group('Events > Transport > Events.Transport.Core > httpProject()', () => {
  const init: IHttpProjectStoreProxyInit = {
    kind: HttpProjectKind,
    pid: 'test-id',
    options: { parallel: true },
    organization: 'test-organization',
  }

  test('dispatches the event', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Transport.Core.httpProject, spy)
    await Events.Transport.Core.httpProject(init)
    globalThis.removeEventListener(EventTypes.Transport.Core.httpProject, spy)
    assert.isTrue(spy.calledOnce)
  })

  test('has the detail object', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Transport.Core.httpProject, spy)
    await Events.Transport.Core.httpProject(init)
    globalThis.removeEventListener(EventTypes.Transport.Core.httpProject, spy)
    const e = spy.args[0][0] as CustomEvent<ContextEventDetailWithResult<IHttpProjectProxyInit>>
    assert.deepEqual(e.detail, { ...init, result: undefined })
  })
})

test.group('Events > Transport > Events.Transport.Http > send()', () => {
  const request: IHttpRequest = new HttpRequest().toJSON()
  const init: RequestInit = { method: 'POST' }

  test('dispatches the event', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Transport.Http.send, spy)
    await Events.Transport.Http.send(request)
    globalThis.removeEventListener(EventTypes.Transport.Http.send, spy)
    assert.isTrue(spy.calledOnce)
  })

  test('has the request on the detail', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Transport.Http.send, spy)
    await Events.Transport.Http.send(request)
    globalThis.removeEventListener(EventTypes.Transport.Http.send, spy)
    const e = spy.args[0][0] as CustomEvent<IHttpRequestDetail>
    assert.equal(e.detail.request, request)
  })

  test('has the optional init on the detail', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Transport.Http.send, spy)
    await Events.Transport.Http.send(request, init)
    globalThis.removeEventListener(EventTypes.Transport.Http.send, spy)
    const e = spy.args[0][0] as CustomEvent<IHttpRequestDetail>
    assert.deepEqual(e.detail.init, init)
  })
})
