import { describe, it, expect, beforeEach, vi } from 'vitest'
import {
  createSession,
  getLiveSession,
  updateLiveSession,
  deleteLiveSession,
  getActiveSessions,
  getActiveSessionByUserId,
  getOrCreateSession,
  clearSessions,
} from '../../../server/utils/liveSessions.js'

vi.mock('../../../server/utils/mediasoup.js', () => ({
  getProducer: vi.fn().mockReturnValue(null),
  getTransport: vi.fn().mockReturnValue(null),
  closeRouter: vi.fn().mockResolvedValue(undefined),
}))

describe('liveSessions', () => {
  const testState = {
    sessionId: null,
  }

  beforeEach(async () => {
    clearSessions()
    const session = await createSession('test-user', 'Test Session')
    testState.sessionId = session.id
  })

  it('creates a session with WebRTC fields', () => {
    const session = getLiveSession(testState.sessionId)
    expect(session).toBeDefined()
    expect(session.id).toBe(testState.sessionId)
    expect(session.userId).toBe('test-user')
    expect(session.label).toBe('Test Session')
    expect(session.routerId).toBeNull()
    expect(session.producerId).toBeNull()
    expect(session.transportId).toBeNull()
  })

  it('updates location', async () => {
    await updateLiveSession(testState.sessionId, { lat: 37.7, lng: -122.4 })
    const session = getLiveSession(testState.sessionId)
    expect(session.lat).toBe(37.7)
    expect(session.lng).toBe(-122.4)
  })

  it('updates WebRTC fields', async () => {
    await updateLiveSession(testState.sessionId, { routerId: 'router-1', producerId: 'producer-1', transportId: 'transport-1' })
    const session = getLiveSession(testState.sessionId)
    expect(session.routerId).toBe('router-1')
    expect(session.producerId).toBe('producer-1')
    expect(session.transportId).toBe('transport-1')
  })

  it('returns hasStream instead of hasSnapshot', async () => {
    await updateLiveSession(testState.sessionId, { producerId: 'producer-1' })
    const active = await getActiveSessions()
    const session = active.find(s => s.id === testState.sessionId)
    expect(session).toBeDefined()
    expect(session.hasStream).toBe(true)
  })

  it('returns hasStream false when no producer', async () => {
    const active = await getActiveSessions()
    const session = active.find(s => s.id === testState.sessionId)
    expect(session).toBeDefined()
    expect(session.hasStream).toBe(false)
  })

  it('deletes a session', async () => {
    await deleteLiveSession(testState.sessionId)
    const session = getLiveSession(testState.sessionId)
    expect(session).toBeUndefined()
  })

  it('getActiveSessionByUserId returns session for same user when active', async () => {
    const found = await getActiveSessionByUserId('test-user')
    expect(found).toBeDefined()
    expect(found.id).toBe(testState.sessionId)
  })

  it('getActiveSessionByUserId returns undefined for unknown user', async () => {
    const found = await getActiveSessionByUserId('other-user')
    expect(found).toBeUndefined()
  })

  it('getActiveSessionByUserId returns undefined for expired session', async () => {
    const session = getLiveSession(testState.sessionId)
    session.updatedAt = Date.now() - 120_000
    const found = await getActiveSessionByUserId('test-user')
    expect(found).toBeUndefined()
  })

  it('getActiveSessions removes expired sessions', async () => {
    const session = getLiveSession(testState.sessionId)
    session.updatedAt = Date.now() - 120_000
    const active = await getActiveSessions()
    expect(active.find(s => s.id === testState.sessionId)).toBeUndefined()
    expect(getLiveSession(testState.sessionId)).toBeUndefined()
  })

  it('getActiveSessions runs cleanup for expired session with producer and transport', async () => {
    const { getProducer, getTransport, closeRouter } = await import('../../../server/utils/mediasoup.js')
    const mockProducer = { close: vi.fn() }
    const mockTransport = { close: vi.fn() }
    getProducer.mockReturnValue(mockProducer)
    getTransport.mockReturnValue(mockTransport)
    closeRouter.mockResolvedValue(undefined)
    await updateLiveSession(testState.sessionId, { producerId: 'p1', transportId: 't1', routerId: 'r1' })
    const session = getLiveSession(testState.sessionId)
    session.updatedAt = Date.now() - 120_000
    const active = await getActiveSessions()
    expect(active.find(s => s.id === testState.sessionId)).toBeUndefined()
    expect(mockProducer.close).toHaveBeenCalled()
    expect(mockTransport.close).toHaveBeenCalled()
    expect(closeRouter).toHaveBeenCalledWith(testState.sessionId)
  })

  it('getOrCreateSession returns existing active session', async () => {
    const session = await getOrCreateSession('test-user', 'New Label')
    expect(session.id).toBe(testState.sessionId)
    expect(session.userId).toBe('test-user')
  })

  it('getOrCreateSession creates new session when none exists', async () => {
    const session = await getOrCreateSession('new-user', 'New Session')
    expect(session.userId).toBe('new-user')
    expect(session.label).toBe('New Session')
  })

  it('getOrCreateSession handles concurrent calls atomically', async () => {
    const promises = Array.from({ length: 5 }, () =>
      getOrCreateSession('concurrent-user', 'Concurrent'),
    )
    const sessions = await Promise.all(promises)
    const uniqueIds = new Set(sessions.map(s => s.id))
    expect(uniqueIds.size).toBe(1)
  })
})
