import { describe, it, expect, vi, beforeEach } from 'vitest'

// Use vi.hoisted() so mock variables are available inside vi.mock() factories
const { mockBroadcast, mockRun, mockGet, mockPrepare } = vi.hoisted(() => {
  const mockRun = vi.fn(() => ({ lastInsertRowid: 1, changes: 1 }))
  const mockGet = vi.fn((): any => ({ count: 1 }))
  const mockPrepare = vi.fn(() => ({
    run: mockRun,
    get: mockGet,
    all: vi.fn(() => []),
  }))
  const mockBroadcast = vi.fn()
  return { mockBroadcast, mockRun, mockGet, mockPrepare }
})

// Mock better-sqlite3 native module to avoid needing compiled bindings
vi.mock('better-sqlite3', () => {
  return {
    default: vi.fn(() => ({
      prepare: mockPrepare,
      pragma: vi.fn(),
      exec: vi.fn(),
      close: vi.fn(),
    })),
  }
})

vi.mock('@/lib/config', () => ({
  config: { dbPath: ':memory:' },
  ensureDirExists: vi.fn(),
}))

vi.mock('@/lib/migrations', () => ({
  runMigrations: vi.fn(),
}))

vi.mock('@/lib/password', () => ({
  hashPassword: vi.fn((p: string) => `hashed:${p}`),
  verifyPassword: vi.fn(() => false),
}))

vi.mock('@/lib/logger', () => ({
  logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() },
}))

vi.mock('@/lib/event-bus', () => ({
  eventBus: { broadcast: mockBroadcast, on: vi.fn(), emit: vi.fn(), setMaxListeners: vi.fn() },
}))

// Import after mocks — the real db_helpers will use our mocked getDatabase
import { db_helpers } from '@/lib/db'

describe('parseMentions', () => {
  it('extracts multiple mentions', () => {
    expect(db_helpers.parseMentions('@alice hello @bob')).toEqual(['alice', 'bob'])
  })

  it('returns empty array when no mentions', () => {
    expect(db_helpers.parseMentions('no mentions here')).toEqual([])
  })

  it('extracts single mention', () => {
    expect(db_helpers.parseMentions('hey @alice')).toEqual(['alice'])
  })

  it('handles @@double — captures word chars after @', () => {
    const result = db_helpers.parseMentions('@@double')
    expect(result).toContain('double')
  })

  it('handles mentions at start and end of string', () => {
    expect(db_helpers.parseMentions('@start and @end')).toEqual(['start', 'end'])
  })

  it('supports hyphen, underscore, and dots in handles', () => {
    expect(db_helpers.parseMentions('ping @agent-code_reviewer.v2 now')).toEqual(['agent-code_reviewer.v2'])
  })

  it('deduplicates repeated mentions case-insensitively', () => {
    expect(db_helpers.parseMentions('@Alice please sync with @alice')).toEqual(['Alice'])
  })

  it('returns empty array for empty string', () => {
    expect(db_helpers.parseMentions('')).toEqual([])
  })
})

describe('logActivity', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('inserts activity into database and broadcasts event', () => {
    db_helpers.logActivity('task_created', 'task', 1, 'alice', 'Created task')

    expect(mockPrepare).toHaveBeenCalled()
    expect(mockRun).toHaveBeenCalledWith(
      'task_created', 'task', 1, 'alice', 'Created task', null, 1,
    )
    expect(mockBroadcast).toHaveBeenCalledWith(
      'activity.created',
      expect.objectContaining({
        type: 'task_created',
        entity_type: 'task',
        entity_id: 1,
        actor: 'alice',
      }),
    )
  })

  it('stringifies data when provided', () => {
    const data = { key: 'value' }
    db_helpers.logActivity('update', 'agent', 2, 'bob', 'Updated agent', data)

    expect(mockRun).toHaveBeenCalledWith(
      'update', 'agent', 2, 'bob', 'Updated agent', JSON.stringify(data), 1,
    )
  })
})

describe('createNotification', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('inserts notification and broadcasts event', () => {
    db_helpers.createNotification('alice', 'mention', 'Mentioned', 'You were mentioned')

    expect(mockRun).toHaveBeenCalledWith(
      'alice', 'mention', 'Mentioned', 'You were mentioned', undefined, undefined, 1,
    )
    expect(mockBroadcast).toHaveBeenCalledWith(
      'notification.created',
      expect.objectContaining({
        recipient: 'alice',
        type: 'mention',
        title: 'Mentioned',
      }),
    )
  })

  it('passes source_type and source_id when provided', () => {
    db_helpers.createNotification('bob', 'alert', 'Alert', 'CPU high', 'agent', 5)

    expect(mockRun).toHaveBeenCalledWith(
      'bob', 'alert', 'Alert', 'CPU high', 'agent', 5, 1,
    )
  })
})

describe('updateAgentStatus', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockGet.mockReturnValue({ id: 42 })
  })

  it('updates agent status in database and broadcasts', () => {
    db_helpers.updateAgentStatus('worker-1', 'busy', 'Processing task')

    expect(mockPrepare).toHaveBeenCalled()
    expect(mockBroadcast).toHaveBeenCalledWith(
      'agent.status_changed',
      expect.objectContaining({
        id: 42,
        name: 'worker-1',
        status: 'busy',
      }),
    )
  })
})
