import { afterEach, describe, expect, test, vi } from 'bun:test'

import { ref } from 'vue'

import { spawnAcpProcess } from '@/app/ai/acp/process'
import { checkForAppUpdate } from '@/app/shell/updater'

import { clearTauriMocks, mockTauriIPC } from '#tests/helpers/tauri/mocks'

afterEach(async () => {
  await clearTauriMocks()
  vi.restoreAllMocks()
  Reflect.deleteProperty(globalThis, 'window')
})

describe('Tauri process helpers', () => {
  test('spawns ACP processes and streams stdout/stdin through plugin-shell', async () => {
    let onEvent: ((event: unknown) => void) | null = null
    const calls: Array<{ cmd: string; args: unknown }> = []
    await mockTauriIPC((cmd, args) => {
      calls.push({ cmd, args })
      if (cmd === 'plugin:shell|spawn') {
        expect(args).toMatchObject({
          program: 'agent-cli',
          args: ['--stdio'],
          options: { encoding: 'raw' }
        })
        onEvent = (args as { onEvent: { onmessage: (event: unknown) => void } }).onEvent.onmessage
        return 42
      }
      return null
    })

    const process = await spawnAcpProcess({
      command: 'agent-cli',
      args: ['--stdio'],
      logId: 'test',
      destroying: () => false,
      onUnexpectedClose: vi.fn()
    })

    const reader = process.output.getReader()
    onEvent?.({ event: 'Stdout', payload: [1, 2, 3] })
    await expect(reader.read()).resolves.toEqual({ done: false, value: new Uint8Array([1, 2, 3]) })

    const writer = process.input.getWriter()
    await writer.write(new Uint8Array([4, 5]))
    await process.child.kill()

    expect(calls.map((call) => call.cmd)).toEqual([
      'plugin:shell|spawn',
      'plugin:shell|stdin_write',
      'plugin:shell|kill'
    ])
    expect(calls[1]?.args).toEqual({ pid: 42, buffer: [4, 5] })
    expect(calls[2]?.args).toEqual({ cmd: 'killChild', pid: 42 })
  })

  test('signals unexpected ACP process close to the output stream', async () => {
    let onEvent: ((event: unknown) => void) | null = null
    const onUnexpectedClose = vi.fn()
    await mockTauriIPC((cmd, args) => {
      if (cmd === 'plugin:shell|spawn') {
        onEvent = (args as { onEvent: { onmessage: (event: unknown) => void } }).onEvent.onmessage
        return 43
      }
      return null
    })

    const process = await spawnAcpProcess({
      command: 'agent-cli',
      args: [],
      logId: 'test',
      destroying: () => false,
      onUnexpectedClose
    })
    const reader = process.output.getReader()

    onEvent?.({ event: 'Terminated', payload: { code: 1, signal: null } })

    await expect(reader.read()).rejects.toThrow('Agent process exited unexpectedly.')
    expect(onUnexpectedClose).toHaveBeenCalled()
  })
})

describe('Tauri updater helper', () => {
  const messages = ref({
    appUpToDate: 'Up to date',
    updateAvailableTitle: 'Update available',
    updateAvailable: ({ version }: { version: string }) => `Version ${version}`,
    updateInstallPrompt: 'Install now?',
    downloadingUpdate: ({ version }: { version: string }) => `Downloading ${version}`,
    updateInstalledTitle: 'Installed',
    updateInstalled: ({ version, size }: { version: string; size: string }) =>
      `Installed ${version}${size}`,
    updateUnavailable: 'Unavailable',
    updateCheckFailed: ({ error }: { error: string }) => `Failed: ${error}`
  })

  test('returns quietly when no Tauri update is available', async () => {
    const calls: string[] = []
    await mockTauriIPC((cmd) => {
      calls.push(cmd)
      if (cmd === 'plugin:updater|check') return null
      return null
    })

    await checkForAppUpdate({ silent: true, messages })

    expect(calls).toEqual(['plugin:updater|check'])
  })

  test('confirms, installs, and relaunches available Tauri updates', async () => {
    const calls: Array<{ cmd: string; args: unknown }> = []
    await mockTauriIPC((cmd, args) => {
      calls.push({ cmd, args })
      if (cmd === 'plugin:updater|check') {
        return {
          rid: 9,
          currentVersion: '0.11.8',
          version: '0.11.9',
          date: null,
          body: 'Release notes',
          rawJson: '{}'
        }
      }
      if (cmd === 'plugin:dialog|confirm') return true
      if (cmd === 'plugin:updater|download_and_install') {
        const onEvent = (args as { onEvent: { onmessage: (event: unknown) => void } }).onEvent
        onEvent.onmessage({ event: 'Started', data: { contentLength: 10 } })
        onEvent.onmessage({ event: 'Progress', data: { chunkLength: 10 } })
      }
      return null
    })

    await checkForAppUpdate({ messages })

    expect(calls.map((call) => call.cmd)).toEqual([
      'plugin:updater|check',
      'plugin:dialog|confirm',
      'plugin:updater|download_and_install',
      'plugin:dialog|message',
      'plugin:process|restart'
    ])
    expect(calls[1]?.args).toMatchObject({
      title: 'Update available',
      kind: 'info'
    })
    expect(calls[2]?.args).toMatchObject({ rid: 9 })
  })
})
