import { EventEmitter } from 'node:events'
import { describe, expect, it, vi } from 'vitest'
import type { ResolvedChannelSettings } from './channel-settings-store'
import { WechatChannelDriver } from './wechat-channel-driver'
import type { WechatSidecarChild } from './wechat-sidecar-client'

class FakeSidecar extends EventEmitter {
  readonly posted: unknown[] = []

  postMessage(message: unknown): void {
    this.posted.push(message)
  }

  kill(): boolean {
    return true
  }
}

const settings: Extract<
  ResolvedChannelSettings,
  { channel: 'weixin' }
> = {
  channel: 'weixin',
  enabled: true,
  accountId: 'bot-account',
  userId: 'bound-user',
  baseUrl: 'https://ilinkai.weixin.qq.com',
  token: 'private-token',
  allowedSenderIds: ['bound-user'],
  allowGroupMessages: false,
  source: 'encrypted',
  readOnly: false
}

describe('WechatChannelDriver', () => {
  it('starts an isolated account, forwards text, and correlates replies', async () => {
    const child = new FakeSidecar()
    const handler = vi.fn()
    const driver = new WechatChannelDriver(
      settings,
      () => child as unknown as WechatSidecarChild
    )

    const starting = driver.start(handler)
    await vi.waitFor(() =>
      expect(child.posted).toContainEqual(
        expect.objectContaining({
          type: 'start_account',
          accountId: 'bot-account',
          token: 'private-token'
        })
      )
    )
    child.emit('message', {
      type: 'status',
      status: 'connected'
    })
    await starting

    child.emit('message', {
      type: 'inbound_message',
      eventId: 'event-1',
      senderId: 'sender-1',
      conversationId: 'sender-1',
      text: '你好',
      attachments: [
        {
          name: '说明.txt',
          mimeType: 'text/plain',
          size: 2,
          kind: 'file',
          dataBase64: 'b2s='
        }
      ]
    })
    await vi.waitFor(() => expect(handler).toHaveBeenCalledOnce())
    expect(handler).toHaveBeenCalledWith(
      expect.objectContaining({
        channel: 'weixin',
        eventId: 'event-1',
        senderId: 'sender-1',
        workMode: 'ask',
        attachments: [
          expect.objectContaining({ name: '说明.txt' })
        ]
      }),
      expect.any(Function)
    )

    const sending = driver.send(
      {
        channel: 'weixin',
        eventId: 'event-1',
        conversationId: 'sender-1',
        recipientId: 'sender-1',
        status: 'completed',
        output: '收到',
        attachments: [
          {
            name: '结果.txt',
            mimeType: 'text/plain',
            size: 2,
            kind: 'file',
            dataBase64: 'b2s='
          }
        ]
      },
      new AbortController().signal
    )
    const reply = child.posted.find(
      (
        message
      ): message is {
        type: 'reply'
        replyId: string
      } =>
        typeof message === 'object' &&
        message !== null &&
        'type' in message &&
        message.type === 'reply'
    )
    expect(reply).toBeDefined()
    expect(reply).toMatchObject({
      attachments: [expect.objectContaining({ name: '结果.txt' })]
    })
    child.emit('message', {
      type: 'reply_result',
      replyId: reply!.replyId,
      ok: true
    })
    await expect(sending).resolves.toBeUndefined()
    driver.stop()
  })

  it('rejects incomplete persisted bindings before launching', async () => {
    const launch = vi.fn(
      () => new FakeSidecar() as unknown as WechatSidecarChild
    )
    const driver = new WechatChannelDriver(
      { ...settings, token: undefined },
      launch
    )
    await expect(driver.start(vi.fn())).rejects.toThrow(
      '尚未完成扫码绑定'
    )
    expect(launch).not.toHaveBeenCalled()
  })

  it('rejects in-flight replies when the sidecar fails', async () => {
    const child = new FakeSidecar()
    const driver = new WechatChannelDriver(
      settings,
      () => child as unknown as WechatSidecarChild
    )
    const starting = driver.start(vi.fn())
    await vi.waitFor(() =>
      expect(child.posted).toContainEqual(
        expect.objectContaining({ type: 'start_account' })
      )
    )
    child.emit('message', {
      type: 'status',
      status: 'connected'
    })
    await starting

    const sending = driver.send(
      {
        channel: 'weixin',
        eventId: 'event-failed',
        conversationId: 'sender-1',
        recipientId: 'sender-1',
        status: 'completed',
        output: '结果'
      },
      new AbortController().signal
    )
    child.emit('message', {
      type: 'status',
      status: 'failed',
      detail: 'Sidecar 已退出'
    })
    await expect(sending).rejects.toThrow('Sidecar 已退出')
    driver.stop()
  })

  it('cancels in-flight sidecar media work when delivery is aborted', async () => {
    const child = new FakeSidecar()
    const driver = new WechatChannelDriver(
      settings,
      () => child as unknown as WechatSidecarChild
    )
    const starting = driver.start(vi.fn())
    await vi.waitFor(() =>
      expect(child.posted).toContainEqual(
        expect.objectContaining({ type: 'start_account' })
      )
    )
    child.emit('message', {
      type: 'status',
      status: 'connected'
    })
    await starting
    const controller = new AbortController()
    const sending = driver.send(
      {
        channel: 'weixin',
        eventId: 'event-cancel',
        conversationId: 'sender-1',
        recipientId: 'sender-1',
        status: 'completed',
        output: '结果'
      },
      controller.signal
    )
    const reply = child.posted.find(
      (
        message
      ): message is {
        type: 'reply'
        replyId: string
      } =>
        typeof message === 'object' &&
        message !== null &&
        'type' in message &&
        message.type === 'reply'
    )
    controller.abort()

    await expect(sending).rejects.toThrow('已取消')
    expect(child.posted).toContainEqual({
      type: 'cancel_reply',
      replyId: reply!.replyId
    })
    driver.stop()
  })
})
