import {expect, test, vi} from 'vitest'
import {createBatchedStore} from '../createBatchedStore'
import {createSessionId} from '../'
import {
  ExampleEvent,
  ExampleSampledEvent,
  ExampleTrace,
} from './exampleEvents.telemetry'

test('Logging an example event', async () => {
  const sendEvents = vi.fn().mockResolvedValue(undefined)
  const {logger} = createBatchedStore(createSessionId(), {
    flushInterval: 100,
    resolveConsent: () => Promise.resolve({status: 'granted'}),
    sendEvents,
  })

  logger.log(ExampleEvent, {foo: 'bar'})

  await new Promise((resolve) => setTimeout(resolve, 200))

  expect(sendEvents.mock.calls?.[0]?.[0]).toMatchObject([
    {
      name: ExampleEvent.name,
      sessionId: /.+/,
      createdAt: /.+/,
      data: {foo: 'bar'},
      type: 'log',
      version: 1,
    },
  ])
})

test('Tracing an async operation that resolves to an invalid value', async () => {
  const sendEvents = vi.fn().mockResolvedValue(undefined)
  const {logger} = createBatchedStore(createSessionId(), {
    flushInterval: 100,
    resolveConsent: () => Promise.resolve({status: 'granted'}),
    sendEvents,
  })

  const trace = logger.trace(ExampleTrace)
  await trace.await(Promise.resolve({step: 'start'}))

  await new Promise((resolve) => setTimeout(resolve, 200))

  expect(sendEvents.mock.calls?.[0]?.[0]).toMatchObject([
    {
      sessionId: /.+/,
      createdAt: /.+/,
      name: 'ExampleTrace',
      traceId: /.+/,
      type: 'trace.start',
      data: undefined,
      context: undefined,
      version: 1,
    },
    {
      sessionId: /.+/,
      createdAt: /.+/,
      data: {step: 'start'},
      name: 'ExampleTrace',
      traceId: /.+/,
      context: undefined,
      type: 'trace.log',
      version: 1,
    },
    {
      sessionId: /.+/,
      createdAt: /.+/,
      name: 'ExampleTrace',
      traceId: /.+/,
      context: undefined,
      data: undefined,
      type: 'trace.complete',
      version: 1,
    },
  ])
})

test('It logs at most n event every "maxSampleRate"', async () => {
  const sendEvents = vi.fn().mockResolvedValue(undefined)
  const {logger} = createBatchedStore(createSessionId(), {
    flushInterval: 100,
    resolveConsent: () => Promise.resolve({status: 'granted'}),
    sendEvents,
  })

  logger.log(ExampleSampledEvent, {sampledAt: new Date().toISOString()})
  logger.log(ExampleSampledEvent, {sampledAt: new Date().toISOString()})
  logger.log(ExampleSampledEvent, {sampledAt: new Date().toISOString()})

  await new Promise((resolve) => setTimeout(resolve, 20))
  logger.log(ExampleSampledEvent, {sampledAt: new Date().toISOString()})
  logger.log(ExampleSampledEvent, {sampledAt: new Date().toISOString()})
  await new Promise((resolve) => setTimeout(resolve, 200))

  const sentEvents = sendEvents.mock.calls?.[0]?.[0]

  expect(sentEvents).toMatchObject([
    {
      sessionId: /.+/,
      createdAt: /.+/,
      data: {sampledAt: /.+/},
      name: 'ExampleSampledEvent',
      type: 'log',
      version: 1,
    },
    {
      sessionId: /.+/,
      createdAt: /.+/,
      data: {sampledAt: /.+/},
      name: 'ExampleSampledEvent',
      type: 'log',
      version: 1,
    },
  ])
})

test('Resuming deferred events', async () => {
  const sendEvents = vi.fn().mockResolvedValue(undefined)
  const {logger} = createBatchedStore(createSessionId(), {
    flushInterval: 100,
    resolveConsent: () => Promise.resolve({status: 'granted'}),
    sendEvents,
  })

  logger.resume([
    {
      createdAt: '2024-01-01T00:00:00.000Z',
      event: ExampleEvent,
      data: {foo: 'bar'},
    },
  ])

  await new Promise((resolve) => setTimeout(resolve, 200))

  expect(sendEvents.mock.calls?.[0]?.[0]).toMatchObject([
    {
      name: ExampleEvent.name,
      sessionId: /.+/,
      createdAt: '2024-01-01T00:00:00.000Z',
      data: {foo: 'bar'},
      type: 'log',
      version: 1,
    },
  ])
})

test('Resuming multiple deferred events preserves order and timestamps', async () => {
  const sendEvents = vi.fn().mockResolvedValue(undefined)
  const {logger} = createBatchedStore(createSessionId(), {
    flushInterval: 100,
    resolveConsent: () => Promise.resolve({status: 'granted'}),
    sendEvents,
  })

  logger.resume([
    {
      createdAt: '2024-01-01T00:00:00.000Z',
      event: ExampleEvent,
      data: {foo: 'bar'},
    },
    {
      createdAt: '2024-01-01T00:00:01.000Z',
      event: ExampleEvent,
      data: {foo: 'bar'},
    },
  ])

  await new Promise((resolve) => setTimeout(resolve, 200))

  const sentEvents = sendEvents.mock.calls?.[0]?.[0]
  expect(sentEvents).toHaveLength(2)
  expect(sentEvents[0].createdAt).toBe('2024-01-01T00:00:00.000Z')
  expect(sentEvents[1].createdAt).toBe('2024-01-01T00:00:01.000Z')
})

test('Resumed events are batched together with new events', async () => {
  const sendEvents = vi.fn().mockResolvedValue(undefined)
  const {logger} = createBatchedStore(createSessionId(), {
    flushInterval: 100,
    resolveConsent: () => Promise.resolve({status: 'granted'}),
    sendEvents,
  })

  logger.resume([
    {
      createdAt: '2024-01-01T00:00:00.000Z',
      event: ExampleEvent,
      data: {foo: 'bar'},
    },
  ])
  logger.log(ExampleEvent, {foo: 'bar'})

  await new Promise((resolve) => setTimeout(resolve, 200))

  const sentEvents = sendEvents.mock.calls?.[0]?.[0]
  expect(sentEvents).toHaveLength(2)
  // Resumed event preserves original timestamp
  expect(sentEvents[0].createdAt).toBe('2024-01-01T00:00:00.000Z')
  // New event has a fresh timestamp
  expect(sentEvents[1].createdAt).not.toBe('2024-01-01T00:00:00.000Z')
})
