import { describe, expect, it, vi } from 'vitest'
import {
  DingTalkChannelDriver,
  createOfficialDingTalkTransportFactory
} from './dingtalk-channel-driver'
import type {
  DingTalkStreamEnvelope,
  DingTalkStreamTransport,
  DingTalkTransportFactory
} from './dingtalk-driver'

const SESSION_WEBHOOK =
  'https://oapi.dingtalk.com/robot/sendBySession?session=opaque'

class FakeTransport implements DingTalkStreamTransport {
  listener?: (envelope: DingTalkStreamEnvelope) => Promise<void>
  readonly stop = vi.fn(async () => undefined)
  readonly replyText = vi.fn(async () => undefined)

  async start(
    listener: (envelope: DingTalkStreamEnvelope) => Promise<void>
  ): Promise<void> {
    this.listener = listener
  }
}

function envelope(
  messageId = 'event-1',
  conversationType = '2'
): DingTalkStreamEnvelope {
  return {
    headers: { messageId },
    data: JSON.stringify({
      conversationId: 'conversation-1',
      conversationType,
      createAt: 1_800_000_000_000,
      isInAtList: conversationType === '2',
      msgId: 'provider-1',
      msgtype: 'text',
      senderStaffId: 'USER-1',
      sessionWebhook: SESSION_WEBHOOK,
      sessionWebhookExpiredTime: 4_000_000_000_000,
      text: { content: '请总结进展' }
    })
  }
}

describe('DingTalkChannelDriver', () => {
  it('adapts group text and consumes only the issued reply context', async () => {
    const transport = new FakeTransport()
    const factory: DingTalkTransportFactory = {
      create: async () => transport
    }
    const driver = new DingTalkChannelDriver({
      clientId: 'client-id',
      clientSecret: 'client-secret',
      allowedSenderIds: ['user-1'],
      transportFactory: factory
    })
    const messages: unknown[] = []
    await driver.start((message) => {
      messages.push(message)
    })

    await transport.listener?.(envelope())
    expect(messages).toEqual([
      {
        channel: 'dingtalk',
        eventId: 'event-1',
        senderId: 'user-1',
        conversationId: 'conversation-1',
        conversationType: 'group',
        text: '请总结进展',
        mentioned: true,
        workMode: 'ask',
        receivedAt: 1_800_000_000_000
      }
    ])

    await driver.send(
      {
        channel: 'dingtalk',
        eventId: 'event-1',
        conversationId: 'conversation-1',
        recipientId: 'user-1',
        status: 'completed',
        output: '已完成'
      },
      new AbortController().signal
    )
    expect(transport.replyText).toHaveBeenCalledWith(
      SESSION_WEBHOOK,
      '已完成'
    )
    await expect(
      driver.send(
        {
          channel: 'dingtalk',
          eventId: 'event-1',
          conversationId: 'conversation-1',
          recipientId: 'user-1',
          status: 'completed',
          output: '重复回复'
        },
        new AbortController().signal
      )
    ).rejects.toThrow('上下文无效')
  })

  it('acks official Stream callbacks before asynchronous processing', async () => {
    const order: string[] = []
    let listener:
      | ((message: {
          headers: { messageId: string }
          data: string
        }) => void)
      | undefined
    const client = {
      registerCallbackListener: vi.fn(
        (
          _topic: string,
          value: (message: {
            headers: { messageId: string }
            data: string
          }) => void
        ) => {
          listener = value
        }
      ),
      socketCallBackResponse: vi.fn(() => {
        order.push('ack')
      }),
      connect: vi.fn(async () => undefined),
      disconnect: vi.fn()
    }
    const fetchImpl = vi.fn(async () => new Response(null, { status: 200 }))
    const factory = createOfficialDingTalkTransportFactory({
      clientFactory: async (credentials) => {
        expect(credentials).toEqual({
          clientId: 'client-id',
          clientSecret: 'client-secret'
        })
        return client
      },
      fetchImpl
    })
    const transport = await factory.create({
      clientId: 'client-id',
      clientSecret: 'client-secret'
    })
    await transport.start(async () => {
      order.push('processed')
    })

    listener?.({
      headers: { messageId: 'stream-1' },
      data: '{}'
    })
    expect(order).toEqual(['ack'])
    await vi.waitFor(() => {
      expect(order).toEqual(['ack', 'processed'])
    })
    await transport.replyText(SESSION_WEBHOOK, '安全回复')
    expect(fetchImpl).toHaveBeenCalledWith(
      SESSION_WEBHOOK,
      expect.objectContaining({
        method: 'POST',
        redirect: 'error'
      })
    )
    expect(client.registerCallbackListener).toHaveBeenCalledWith(
      '/v1.0/im/bot/messages/get',
      expect.any(Function)
    )
    expect(client.socketCallBackResponse).toHaveBeenCalledWith(
      'stream-1',
      { status: 'SUCCESS' }
    )
  })
})
