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

const {
  existsSyncMock,
  spawnMock,
  spawnSyncMock,
} = vi.hoisted(() => ({
  existsSyncMock: vi.fn(),
  spawnMock: vi.fn(),
  spawnSyncMock: vi.fn(),
}))

vi.mock('node:child_process', () => ({
  spawn: spawnMock,
  spawnSync: spawnSyncMock,
}))

vi.mock('node:fs', () => ({
  existsSync: existsSyncMock,
}))

const {
  buildNodeNotifierNotificationOptions,
  buildSystemNotificationOptions,
  resolveMacNotificationSenderBundleId,
  sendInteractiveNodeNotifierNotification,
  sendNodeNotifierNotification,
} = await import('../src/dimsdk/pages/utils/system-notify.ts')

const realProcess = globalThis.process

function setProcess(value: NodeJS.Process | undefined): void {
  Object.defineProperty(globalThis, 'process', {
    configurable: true,
    value,
  })
}

afterEach(() => {
  existsSyncMock.mockReset()
  spawnMock.mockReset()
  spawnSyncMock.mockReset()
  setProcess(realProcess)
})

describe('system notify helpers', () => {
  it('resolves mac notification senders from explicit env or terminal mapping', () => {
    expect(resolveMacNotificationSenderBundleId({
      DIMCODE_NOTIFICATION_SENDER: 'com.example.Terminal',
    })).toBe('com.example.Terminal')

    expect(resolveMacNotificationSenderBundleId({
      TERM_PROGRAM: 'WezTerm',
    })).toBe('com.github.wez.wezterm')
  })

  it('builds sanitized notification options with an existing absolute icon path', () => {
    existsSyncMock.mockImplementation((target: string) => target === '/tmp/icon.png')

    expect(buildSystemNotificationOptions(
      '  hello\tworld  ',
      'message\u0007 body',
      '/tmp/icon.png',
    )).toEqual({
      title: 'hello world',
      message: 'message body',
      appName: 'DimCode',
      icon: '/tmp/icon.png',
    })
  })

  it('builds darwin node-notifier options with sender metadata', () => {
    const options = buildNodeNotifierNotificationOptions(
      'darwin',
      'Build done',
      'All green',
      undefined,
      {
        DIMCODE_NOTIFICATION_SENDER: 'com.example.Terminal',
      },
    )

    expect(options).toEqual({
      title: 'Build done',
      message: 'All green',
      sender: 'com.example.Terminal',
      activate: 'com.example.Terminal',
    })
  })

  it('sends node-notifier notifications through the typed notifier interface', async () => {
    setProcess({
      ...realProcess,
      env: {},
    } as NodeJS.Process)

    const notify = vi.fn((options: Record<string, unknown>, cb?: (err: unknown) => void) => {
      cb?.(null)
      expect(options).toMatchObject({
        title: 'Build done',
        message: 'All green',
        appName: 'DimCode',
      })
    })

    await expect(sendNodeNotifierNotification(
      { notify },
      'linux',
      'Build done',
      'All green',
    )).resolves.toBe(true)
  })

  it('sends interactive notifications and maps responses without legacy casts', () => {
    setProcess({
      ...realProcess,
      env: {
        DIMCODE_NOTIFICATION_SENDER: 'com.example.Terminal',
      },
    } as NodeJS.Process)

    const onInteraction = vi.fn()
    const notify = vi.fn((
      options: Record<string, unknown>,
      cb?: (err: unknown, response?: unknown, metadata?: unknown) => void,
    ) => {
      expect(options).toMatchObject({
        title: 'Need input',
        message: 'Choose next step',
        sender: 'com.example.Terminal',
        activate: 'com.example.Terminal',
        actions: ['Retry'],
        closeLabel: 'Dismiss',
        reply: true,
        timeout: 5,
      })
      cb?.(null, 'Retry', {})
    })

    expect(sendInteractiveNodeNotifierNotification(
      { notify },
      'darwin',
      {
        titleRaw: 'Need input',
        messageRaw: 'Choose next step',
        actions: ['Retry'],
        closeLabel: 'Dismiss',
        reply: true,
        timeoutSeconds: 5,
        onInteraction,
      },
    )).toBe(true)

    expect(onInteraction).toHaveBeenCalledWith({
      type: 'action',
      value: 'Retry',
    })
  })
})
