import type {
  ChannelInboundText,
  ChannelResultMessage
} from '../../shared/channel-contracts'
import type { ChannelDriver, ChannelInboundHandler } from './channel-driver'
import {
  WeComDriver,
  type WeComInboundMessage,
  type WeComReplyContext,
  type WeComTransportFactory
} from './wecom-driver'

const DEFAULT_MAXIMUM_REPLY_CONTEXTS = 1_000

type ReplyRecord = {
  context: WeComReplyContext
  conversationId: string
  senderId: string
}

export type WeComChannelDriverOptions = {
  botId: string
  secret: string
  transportFactory?: WeComTransportFactory
  maximumReplyContexts?: number
}

function maximumReplyContexts(value: number | undefined): number {
  const candidate = value ?? DEFAULT_MAXIMUM_REPLY_CONTEXTS
  if (!Number.isSafeInteger(candidate) || candidate < 1) {
    throw new Error('企业微信回复上下文容量无效')
  }
  return candidate
}

function resultText(message: ChannelResultMessage): string {
  return message.output?.trim() || message.error?.trim() || '请求已完成'
}

export class WeComChannelDriver implements ChannelDriver {
  readonly channel = 'wecom'

  private readonly driver: WeComDriver
  private readonly maximumContexts: number
  private readonly replyContexts = new Map<string, ReplyRecord>()
  private handler?: ChannelInboundHandler

  constructor(options: WeComChannelDriverOptions) {
    this.maximumContexts = maximumReplyContexts(
      options.maximumReplyContexts
    )
    this.driver = new WeComDriver({
      botId: options.botId,
      secret: options.secret,
      onMessage: (message) => this.handleMessage(message),
      ...(options.transportFactory
        ? { transportFactory: options.transportFactory }
        : {})
    })
  }

  async start(handler: ChannelInboundHandler): Promise<void> {
    this.handler = handler
    try {
      await this.driver.start()
    } catch {
      this.handler = undefined
      throw new Error('企业微信通道启动失败')
    }
  }

  async send(
    message: ChannelResultMessage,
    signal: AbortSignal
  ): Promise<void> {
    const record = this.replyContexts.get(message.eventId)
    if (
      !record ||
      message.channel !== this.channel ||
      message.conversationId !== record.conversationId ||
      message.recipientId !== record.senderId
    ) {
      throw new Error('企业微信回复上下文无效或已过期')
    }

    try {
      signal.throwIfAborted()
      await this.driver.reply(record.context, {
        text: resultText(message)
      })
    } catch {
      throw new Error('企业微信消息回复失败')
    } finally {
      this.replyContexts.delete(message.eventId)
    }
  }

  async stop(): Promise<void> {
    this.handler = undefined
    this.replyContexts.clear()
    try {
      await this.driver.stop()
    } catch {
      throw new Error('企业微信通道停止失败')
    }
  }

  private async handleMessage(message: WeComInboundMessage): Promise<void> {
    const handler = this.handler
    if (!handler) {
      return
    }

    this.replyContexts.set(message.eventId, {
      context: message.replyContext,
      conversationId: message.conversationId,
      senderId: message.userId
    })
    this.enforceContextLimit()
    const inbound: ChannelInboundText = {
      channel: this.channel,
      eventId: message.eventId,
      senderId: message.userId,
      conversationId: message.conversationId,
      conversationType:
        message.chatType === 'group' ? 'group' : 'direct',
      text: message.text,
      mentioned: message.mentionedBot,
      workMode: 'ask',
      ...(message.createdAt === undefined
        ? {}
        : { receivedAt: message.createdAt })
    }
    await handler(inbound, () => undefined)
  }

  private enforceContextLimit(): void {
    while (this.replyContexts.size > this.maximumContexts) {
      const oldest = this.replyContexts.keys().next().value
      if (typeof oldest !== 'string') {
        return
      }
      this.replyContexts.delete(oldest)
    }
  }
}
