import { test } from '@japa/runner'
import sinon from 'sinon'
import { EventTypes } from '../../../src/events/EventTypes.js'
import { Events } from '../../../src/events/Events.js'
import { IHttpCookie } from '../../../src/models/HttpCookie.js'
import { ContextUpdateEvent, ContextUpdateEventDetail, ContextChangeRecord } from '../../../src/events/BaseEvents.js'
import { ensureUnique } from './EventsTestHelpers.js'
import './events_polyfills.js'

test.group('Events > Cookie > EventTypes.Cookie', () => {
  test('has the namespace', ({ assert }) => {
    assert.typeOf(EventTypes.Cookie, 'object')
  })

  test('has "{prop}" property')
    .with([
      ['listAll', 'sessioncookielistall'],
      ['listDomain', 'sessioncookielistdomain'],
      ['listUrl', 'sessioncookielisturl'],
      ['delete', 'sessioncookiedelete'],
      ['deleteUrl', 'sessioncookiedeleteurl'],
      ['update', 'sessioncookieupdate'],
      ['updateBulk', 'sessioncookieupdatebulk'],
    ])
    .run(({ assert }, [prop, value]) => {
      // @ts-expect-error Used in testing
      assert.equal(EventTypes.Cookie[prop], value)
    })

  test('has State namespace', ({ assert }) => {
    assert.typeOf(EventTypes.Cookie.State, 'object')
  })

  test('has frozen State namespace', ({ assert }) => {
    assert.throws(() => {
      // @ts-expect-error Used in testing
      EventTypes.Cookie.State = { read: '' }
    })
  })

  test('has "{prop}" property for State')
    .with([
      ['update', 'sessioncookiestateupdate'],
      ['delete', 'sessioncookiestatedelete'],
    ])
    .run(({ assert }, [prop, value]) => {
      // @ts-expect-error Used in testing
      assert.equal(EventTypes.Cookie.State[prop], value)
    })

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

  test('has unique events for State namespace', () => {
    ensureUnique('EventTypes.Cookie.State', EventTypes.Cookie.State)
  })
})

test.group('Events > Cookie > Events.Cookie > listAll()', () => {
  test('dispatches the event', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.listAll, spy)
    // In Node.js, document.body is not available.
    // Assuming Events.Cookie.listAll dispatches globally or handles a null/undefined target.
    Events.Cookie.listAll()
    globalThis.removeEventListener(EventTypes.Cookie.listAll, spy)
    assert.isTrue(spy.calledOnce)
  })
})

test.group('Events > Cookie > Events.Cookie > listDomain()', () => {
  const domain = 'dot.com'

  test('dispatches the event', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.listDomain, spy)
    Events.Cookie.listDomain(domain)
    globalThis.removeEventListener(EventTypes.Cookie.listDomain, spy)
    assert.isTrue(spy.calledOnce)
  })

  test('has the domain property', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.listDomain, spy)
    Events.Cookie.listDomain(domain)
    globalThis.removeEventListener(EventTypes.Cookie.listDomain, spy)
    const e = spy.args[0][0] as CustomEvent
    assert.deepEqual(e.detail.domain, domain)
  })
})

test.group('Events > Cookie > Events.Cookie > listUrl()', () => {
  const url = 'https://dot.com/path'

  test('dispatches the event', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.listUrl, spy)
    Events.Cookie.listUrl(url)
    globalThis.removeEventListener(EventTypes.Cookie.listUrl, spy)
    assert.isTrue(spy.calledOnce)
  })

  test('has the url property', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.listUrl, spy)
    Events.Cookie.listUrl(url)
    globalThis.removeEventListener(EventTypes.Cookie.listUrl, spy)
    const e = spy.args[0][0] as CustomEvent
    assert.deepEqual(e.detail.url, url)
  })
})

test.group('Events > Cookie > Events.Cookie > delete()', () => {
  const cookie: IHttpCookie = {
    name: 'a',
    value: 'b',
    sameSite: 'no_restriction',
  }

  test('dispatches the event', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.delete, spy)
    Events.Cookie.delete([cookie])
    globalThis.removeEventListener(EventTypes.Cookie.delete, spy)
    assert.isTrue(spy.calledOnce)
  })

  test('has the cookies property', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.delete, spy)
    Events.Cookie.delete([cookie])
    globalThis.removeEventListener(EventTypes.Cookie.delete, spy)
    const e = spy.args[0][0] as CustomEvent
    assert.deepEqual(e.detail.cookies, [cookie])
  })
})

test.group('Events > Cookie > Events.Cookie > deleteUrl()', () => {
  const url = 'https://dot.com/path'

  test('dispatches the event', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.deleteUrl, spy)
    Events.Cookie.deleteUrl(url)
    globalThis.removeEventListener(EventTypes.Cookie.deleteUrl, spy)
    assert.isTrue(spy.calledOnce)
  })

  test('has the url property', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.deleteUrl, spy)
    Events.Cookie.deleteUrl(url)
    globalThis.removeEventListener(EventTypes.Cookie.deleteUrl, spy)
    const e = spy.args[0][0] as CustomEvent
    assert.deepEqual(e.detail.url, url)
  })

  test('has the optional name property', async ({ assert }) => {
    const name = 'test name'
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.deleteUrl, spy)
    Events.Cookie.deleteUrl(url, name)
    globalThis.removeEventListener(EventTypes.Cookie.deleteUrl, spy)
    const e = spy.args[0][0] as CustomEvent
    assert.deepEqual(e.detail.name, name)
  })
})

test.group('Events > Cookie > Events.Cookie > update()', () => {
  const cookie: IHttpCookie = {
    name: 'a',
    value: 'b',
    sameSite: 'no_restriction',
  }

  test('dispatches the event', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.update, spy)
    Events.Cookie.update(cookie)
    globalThis.removeEventListener(EventTypes.Cookie.update, spy)
    assert.isTrue(spy.calledOnce)
  })

  test('has the detail property', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.update, spy)
    Events.Cookie.update(cookie)
    globalThis.removeEventListener(EventTypes.Cookie.update, spy)
    const e = spy.args[0][0] as ContextUpdateEvent<IHttpCookie>
    const detail = e.detail as ContextUpdateEventDetail<IHttpCookie>
    assert.deepEqual(detail.item, cookie)
  })
})

test.group('Events > Cookie > Events.Cookie > updateBulk()', () => {
  const cookie: IHttpCookie = {
    name: 'a',
    value: 'b',
    sameSite: 'no_restriction',
  }

  test('dispatches the event', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.updateBulk, spy)
    Events.Cookie.updateBulk([cookie])
    globalThis.removeEventListener(EventTypes.Cookie.updateBulk, spy)
    assert.isTrue(spy.calledOnce)
  })

  test('has the cookies property', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.updateBulk, spy)
    Events.Cookie.updateBulk([cookie])
    globalThis.removeEventListener(EventTypes.Cookie.updateBulk, spy)
    const e = spy.args[0][0] as CustomEvent
    assert.deepEqual(e.detail.cookies, [cookie])
  })
})

test.group('Events > Cookie > Events.Cookie > State.delete()', () => {
  const cookie: IHttpCookie = {
    name: 'a',
    value: 'b',
    sameSite: 'no_restriction',
  }

  test('dispatches the event', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.State.delete, spy)
    Events.Cookie.State.delete(cookie)
    globalThis.removeEventListener(EventTypes.Cookie.State.delete, spy)
    assert.isTrue(spy.calledOnce)
  })

  test('has the cookie property', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.State.delete, spy)
    Events.Cookie.State.delete(cookie)
    globalThis.removeEventListener(EventTypes.Cookie.State.delete, spy)
    const e = spy.args[0][0] as CustomEvent
    assert.deepEqual(e.detail.cookie, cookie)
  })
})

test.group('Events > Cookie > Events.Cookie > State.update()', () => {
  const cookie: IHttpCookie = {
    name: 'a',
    value: 'b',
    sameSite: 'no_restriction',
  }

  const record: ContextChangeRecord<IHttpCookie> = {
    item: cookie,
    key: 'null',
  }

  test('dispatches the event', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.State.update, spy)
    Events.Cookie.State.update(record)
    globalThis.removeEventListener(EventTypes.Cookie.State.update, spy)
    assert.isTrue(spy.calledOnce)
  })

  test('has the change record', async ({ assert }) => {
    const spy = sinon.spy()
    globalThis.addEventListener(EventTypes.Cookie.State.update, spy)
    Events.Cookie.State.update(record)
    globalThis.removeEventListener(EventTypes.Cookie.State.update, spy)
    const e = spy.args[0][0] as CustomEvent
    assert.deepEqual(e.detail, record)
  })
})
