import { describe, test, expect, beforeEach, mock } from 'bun:test'

// Mock config before importing modules
const mockConfig = {
  port: 3000,
  host: '0.0.0.0',
  apiKeys: ['test-api-key'],
  baseUrl: 'http://localhost:3000',
  pollTimeout: 8,
  heartbeatInterval: 20,
  jwtExpiresIn: 3600,
  disconnectTimeout: 300,
  webCorsOrigins: [],
  wsIdleTimeout: 30,
  wsKeepaliveInterval: 20,
}

mock.module('../config', () => ({
  config: mockConfig,
  getBaseUrl: () => 'http://localhost:3000',
}))

import { storeReset, storeCreateEnvironment } from '../store'
import {
  createSession,
  createCodeSession,
  getSession,
  updateSessionTitle,
  updateSessionStatus,
  archiveSession,
  incrementEpoch,
  listSessions,
  listSessionSummaries,
  listSessionSummariesByUsername,
  listSessionsByEnvironment,
} from '../services/session'
import {
  registerEnvironment,
  deregisterEnvironment,
  getEnvironment,
  updatePollTime,
  listActiveEnvironments,
  listActiveEnvironmentsResponse,
  listActiveEnvironmentsByUsername,
  reconnectEnvironment,
} from '../services/environment'
import { normalizePayload, publishSessionEvent } from '../services/transport'
import {
  getEventBus,
  removeEventBus,
  getAllEventBuses,
} from '../transport/event-bus'

// ---------- Session Service ----------

describe('Session Service', () => {
  beforeEach(() => {
    storeReset()
    for (const [key] of getAllEventBuses()) {
      removeEventBus(key)
    }
  })

  describe('createSession', () => {
    test('creates a session with defaults', () => {
      const resp = createSession({})
      expect(resp.id).toMatch(/^session_/)
      expect(resp.status).toBe('idle')
      expect(resp.source).toBe('remote-control')
      expect(resp.environment_id).toBeNull()
      expect(resp.worker_epoch).toBe(0)
      expect(resp.created_at).toBeGreaterThan(0)
    })

    test('creates a session with all options', () => {
      const env = storeCreateEnvironment({ secret: 's' })
      const resp = createSession({
        environment_id: env.id,
        title: 'My Session',
        source: 'cli',
        permission_mode: 'auto',
      })
      expect(resp.environment_id).toBe(env.id)
      expect(resp.title).toBe('My Session')
      expect(resp.source).toBe('cli')
      expect(resp.permission_mode).toBe('auto')
    })

    test('creates session with username', () => {
      const resp = createSession({ username: 'alice' })
      expect(resp.username).toBe('alice')
    })
  })

  describe('createCodeSession', () => {
    test('creates a code session with cse_ prefix', () => {
      const resp = createCodeSession({})
      expect(resp.id).toMatch(/^cse_/)
    })
  })

  describe('getSession', () => {
    test('returns null for non-existent session', () => {
      expect(getSession('nope')).toBeNull()
    })

    test('returns created session', () => {
      const created = createSession({})
      const fetched = getSession(created.id)
      expect(fetched).not.toBeNull()
      expect(fetched!.id).toBe(created.id)
    })
  })

  describe('updateSessionTitle', () => {
    test('updates title', () => {
      const s = createSession({})
      updateSessionTitle(s.id, 'New Title')
      expect(getSession(s.id)?.title).toBe('New Title')
    })
  })

  describe('updateSessionStatus', () => {
    test('updates status', () => {
      const s = createSession({})
      updateSessionStatus(s.id, 'active')
      expect(getSession(s.id)?.status).toBe('active')
    })
  })

  describe('archiveSession', () => {
    test('sets status to archived and removes event bus', () => {
      const s = createSession({})
      // Create event bus for this session
      getEventBus(s.id)
      archiveSession(s.id)
      expect(getSession(s.id)?.status).toBe('archived')
      expect(getAllEventBuses().has(s.id)).toBe(false)
    })
  })

  describe('incrementEpoch', () => {
    test('increments epoch by 1', () => {
      const s = createSession({})
      expect(incrementEpoch(s.id)).toBe(1)
      expect(incrementEpoch(s.id)).toBe(2)
      expect(getSession(s.id)?.worker_epoch).toBe(2)
    })

    test('throws for non-existent session', () => {
      expect(() => incrementEpoch('nope')).toThrow('Session not found')
    })
  })

  describe('listSessions', () => {
    test('returns all sessions', () => {
      createSession({})
      createSession({})
      expect(listSessions()).toHaveLength(2)
    })
  })

  describe('listSessionSummaries', () => {
    test('returns summaries with correct fields', () => {
      createSession({ title: 'Test' })
      const summaries = listSessionSummaries()
      expect(summaries).toHaveLength(1)
      expect(summaries[0].title).toBe('Test')
      expect(summaries[0].updated_at).toBeGreaterThan(0)
      // Summary should not have environment_id
      expect('environment_id' in summaries[0]).toBe(false)
    })
  })

  describe('listSessionSummariesByUsername', () => {
    test('filters by username', () => {
      createSession({ username: 'alice' })
      createSession({ username: 'bob' })
      expect(listSessionSummariesByUsername('alice')).toHaveLength(1)
    })
  })

  describe('listSessionsByEnvironment', () => {
    test('filters by environment', () => {
      const env = storeCreateEnvironment({ secret: 's' })
      createSession({ environment_id: env.id })
      createSession({})
      expect(listSessionsByEnvironment(env.id)).toHaveLength(1)
    })
  })
})

// ---------- Environment Service ----------

describe('Environment Service', () => {
  beforeEach(() => {
    storeReset()
  })

  describe('registerEnvironment', () => {
    test('registers environment with defaults', () => {
      const result = registerEnvironment({})
      expect(result.environment_id).toMatch(/^env_/)
      expect(result.environment_secret).toBe('test-api-key')
      expect(result.status).toBe('active')
    })

    test('registers with options', () => {
      const result = registerEnvironment({
        machine_name: 'mac1',
        directory: '/home/user',
        branch: 'main',
        git_repo_url: 'https://github.com/test/repo',
        max_sessions: 5,
        worker_type: 'custom',
      })
      const env = getEnvironment(result.environment_id)
      expect(env?.machineName).toBe('mac1')
      expect(env?.directory).toBe('/home/user')
      expect(env?.maxSessions).toBe(5)
    })

    test('registers with username', () => {
      const result = registerEnvironment({ username: 'alice' })
      const env = getEnvironment(result.environment_id)
      expect(env?.username).toBe('alice')
    })
  })

  describe('deregisterEnvironment', () => {
    test('sets status to deregistered', () => {
      const result = registerEnvironment({})
      deregisterEnvironment(result.environment_id)
      const env = getEnvironment(result.environment_id)
      expect(env?.status).toBe('deregistered')
    })
  })

  describe('updatePollTime', () => {
    test('updates lastPollAt', () => {
      const result = registerEnvironment({})
      const before = getEnvironment(result.environment_id)?.lastPollAt
      // Small delay to ensure time difference
      updatePollTime(result.environment_id)
      const after = getEnvironment(result.environment_id)?.lastPollAt
      expect(after!.getTime()).toBeGreaterThanOrEqual(before!.getTime())
    })
  })

  describe('listActiveEnvironments', () => {
    test('returns active environments', () => {
      registerEnvironment({})
      registerEnvironment({})
      expect(listActiveEnvironments()).toHaveLength(2)
    })
  })

  describe('listActiveEnvironmentsResponse', () => {
    test('returns response format', () => {
      registerEnvironment({ machine_name: 'mac1' })
      const envs = listActiveEnvironmentsResponse()
      expect(envs).toHaveLength(1)
      expect(envs[0].machine_name).toBe('mac1')
      expect(envs[0].last_poll_at).toBeGreaterThan(0)
    })
  })

  describe('listActiveEnvironmentsByUsername', () => {
    test('filters by username', () => {
      registerEnvironment({ username: 'alice' })
      registerEnvironment({ username: 'bob' })
      expect(listActiveEnvironmentsByUsername('alice')).toHaveLength(1)
    })
  })

  describe('reconnectEnvironment', () => {
    test('sets status back to active', () => {
      const result = registerEnvironment({})
      deregisterEnvironment(result.environment_id)
      expect(getEnvironment(result.environment_id)?.status).toBe('deregistered')
      reconnectEnvironment(result.environment_id)
      expect(getEnvironment(result.environment_id)?.status).toBe('active')
    })
  })
})

// ---------- Transport Service ----------

describe('Transport Service', () => {
  beforeEach(() => {
    storeReset()
    for (const [key] of getAllEventBuses()) {
      removeEventBus(key)
    }
  })

  describe('normalizePayload', () => {
    test('handles string payload', () => {
      const result = normalizePayload('user', 'hello world')
      expect(result.content).toBe('hello world')
      expect(result.raw).toBe('hello world')
    })

    test('handles null payload', () => {
      const result = normalizePayload('user', null)
      expect(result.content).toBe('')
      expect(result.raw).toBeNull()
    })

    test('handles object with direct content', () => {
      const result = normalizePayload('user', { content: 'direct text' })
      expect(result.content).toBe('direct text')
    })

    test('handles object with message.content string', () => {
      const result = normalizePayload('assistant', {
        message: { role: 'assistant', content: 'reply' },
      })
      expect(result.content).toBe('reply')
    })

    test('handles object with message.content array', () => {
      const result = normalizePayload('assistant', {
        message: {
          content: [
            { type: 'text', text: 'hello ' },
            { type: 'text', text: 'world' },
          ],
        },
      })
      expect(result.content).toBe('hello world')
    })

    test('preserves tool fields', () => {
      const result = normalizePayload('tool_use', {
        tool_name: 'Bash',
        tool_input: { cmd: 'ls' },
      })
      expect(result.tool_name).toBe('Bash')
      expect(result.tool_input).toEqual({ cmd: 'ls' })
    })

    test('preserves permission fields', () => {
      const result = normalizePayload('permission', {
        request_id: 'req_1',
        approved: true,
        updated_input: { cmd: 'ls -la' },
      })
      expect(result.request_id).toBe('req_1')
      expect(result.approved).toBe(true)
      expect(result.updated_input).toEqual({ cmd: 'ls -la' })
    })

    test('preserves message field', () => {
      const msg = { role: 'user', content: 'hi' }
      const result = normalizePayload('user', { message: msg })
      expect(result.message).toEqual(msg)
    })

    test('preserves uuid field', () => {
      const result = normalizePayload('user', {
        uuid: 'msg_123',
        content: 'hi',
      })
      expect(result.uuid).toBe('msg_123')
    })

    test('preserves isSynthetic field', () => {
      const result = normalizePayload('user', {
        content: 'scheduled job: refresh analytics cache',
        isSynthetic: true,
      })
      expect(result.isSynthetic).toBe(true)
    })

    test('uses name as tool_name fallback', () => {
      const result = normalizePayload('tool', { name: 'Read' })
      expect(result.tool_name).toBe('Read')
    })

    test('uses input as tool_input fallback', () => {
      const result = normalizePayload('tool', { input: { path: '/tmp' } })
      expect(result.tool_input).toEqual({ path: '/tmp' })
    })

    test('handles empty content array', () => {
      const result = normalizePayload('assistant', {
        message: { content: [] },
      })
      expect(result.content).toBe('')
    })

    test('preserves task_state fields', () => {
      const result = normalizePayload('task_state', {
        task_list_id: 'team-alpha',
        tasks: [{ id: '1', subject: 'Task 1', status: 'pending' }],
      })
      expect(result.task_list_id).toBe('team-alpha')
      expect(result.tasks).toEqual([
        { id: '1', subject: 'Task 1', status: 'pending' },
      ])
    })

    test('preserves status metadata for conversation reset events', () => {
      const result = normalizePayload('status', {
        status: 'conversation_cleared',
        subtype: 'status',
        message: 'conversation_cleared',
      })
      expect(result.status).toBe('conversation_cleared')
      expect(result.subtype).toBe('status')
      expect(result.message).toBe('conversation_cleared')
    })

    test('handles undefined payload', () => {
      const result = normalizePayload('user', undefined)
      expect(result.content).toBe('')
    })
  })

  describe('publishSessionEvent', () => {
    test('publishes event to session bus', () => {
      const event = publishSessionEvent(
        's1',
        'user',
        { content: 'hello' },
        'outbound',
      )
      expect(event.type).toBe('user')
      expect(event.direction).toBe('outbound')
      expect(event.sessionId).toBe('s1')
      expect(event.seqNum).toBe(1)
    })

    test('normalizes payload before publishing', () => {
      const event = publishSessionEvent(
        's1',
        'assistant',
        { message: { content: 'reply' } },
        'inbound',
      )
      const payload = event.payload as Record<string, unknown>
      expect(payload.content).toBe('reply')
    })
  })
})
