import type { ChannelInboundText } from '../../shared/channel-contracts'
import type { ChannelExecutor } from './channel-driver'
import { ChannelService } from './channel-service'
import {
  DingTalkChannelDriver,
  type DingTalkChannelDriverOptions
} from './dingtalk-channel-driver'
import {
  normalizeDingTalkStaffId,
  type DingTalkTransportFactory
} from './dingtalk-driver'
import {
  WeComChannelDriver,
  type WeComChannelDriverOptions
} from './wecom-channel-driver'
import type { WeComTransportFactory } from './wecom-driver'

type ChannelEnvironmentConfig =
  | {
      channel: 'dingtalk'
      clientId: string
      clientSecret: string
      allowedSenderIds: readonly string[]
      allowGroupMessages: boolean
    }
  | {
      channel: 'wecom'
      botId: string
      secret: string
      allowedSenderIds: readonly string[]
      allowGroupMessages: boolean
    }

export type EnvironmentChannelService = Pick<
  ChannelService,
  'start' | 'stop'
>

export type EnvironmentChannelBootstrapOptions = {
  executor: ChannelExecutor
  env?: NodeJS.ProcessEnv
  dingtalkTransportFactory?: DingTalkTransportFactory
  wecomTransportFactory?: WeComTransportFactory
  createDingTalkDriver?: (
    options: DingTalkChannelDriverOptions
  ) => DingTalkChannelDriver
  createWeComDriver?: (
    options: WeComChannelDriverOptions
  ) => WeComChannelDriver
  createService?: (
    driver: DingTalkChannelDriver | WeComChannelDriver,
    executor: ChannelExecutor,
    options: {
      allowedSenderIds: readonly string[]
      allowGroupMessages: boolean
    }
  ) => EnvironmentChannelService
  onStartError?: (channel: string, error: string) => void
}

function optionalCredential(
  env: NodeJS.ProcessEnv,
  name: string
): string | undefined {
  const value = env[name]
  if (value === undefined || value.trim() === '') {
    return undefined
  }
  return value.trim()
}

function parseBoolean(
  env: NodeJS.ProcessEnv,
  name: string
): boolean {
  const raw = env[name]
  if (raw === undefined || raw === '') {
    return false
  }
  if (raw === 'true') {
    return true
  }
  if (raw === 'false') {
    return false
  }
  throw new Error(`${name} 必须是 true 或 false`)
}

function parseList(
  env: NodeJS.ProcessEnv,
  name: string
): readonly string[] {
  const raw = env[name]
  if (raw === undefined || raw === '') {
    return []
  }
  const values = raw.split(',').map((value) => value.trim())
  if (values.some((value) => value === '')) {
    throw new Error(`${name} 包含空白身份`)
  }
  return [...new Set(values)]
}

export function parseChannelEnvironment(
  env: NodeJS.ProcessEnv
): readonly ChannelEnvironmentConfig[] {
  const configs: ChannelEnvironmentConfig[] = []
  const dingTalkClientId = optionalCredential(
    env,
    'GOODBUDDY_DINGTALK_CLIENT_ID'
  )
  const dingTalkClientSecret = optionalCredential(
    env,
    'GOODBUDDY_DINGTALK_CLIENT_SECRET'
  )
  const dingTalkAllowedSenderIds = parseList(
    env,
    'GOODBUDDY_DINGTALK_ALLOWED_SENDERS'
  ).map(normalizeDingTalkStaffId)
  const dingTalkAllowGroupMessages = parseBoolean(
    env,
    'GOODBUDDY_DINGTALK_ALLOW_GROUPS'
  )
  if (
    dingTalkClientId &&
    dingTalkClientSecret &&
    dingTalkAllowedSenderIds.length > 0
  ) {
    configs.push({
      channel: 'dingtalk',
      clientId: dingTalkClientId,
      clientSecret: dingTalkClientSecret,
      allowedSenderIds: dingTalkAllowedSenderIds,
      allowGroupMessages: dingTalkAllowGroupMessages
    })
  }

  const weComBotId = optionalCredential(
    env,
    'GOODBUDDY_WECOM_BOT_ID'
  )
  const weComSecret = optionalCredential(
    env,
    'GOODBUDDY_WECOM_SECRET'
  )
  const weComAllowedSenderIds = parseList(
    env,
    'GOODBUDDY_WECOM_ALLOWED_SENDERS'
  )
  const weComAllowGroupMessages = parseBoolean(
    env,
    'GOODBUDDY_WECOM_ALLOW_GROUPS'
  )
  if (
    weComBotId &&
    weComSecret &&
    weComAllowedSenderIds.length > 0
  ) {
    configs.push({
      channel: 'wecom',
      botId: weComBotId,
      secret: weComSecret,
      allowedSenderIds: weComAllowedSenderIds,
      allowGroupMessages: weComAllowGroupMessages
    })
  }
  return configs
}

export function startEnvironmentChannels(
  options: EnvironmentChannelBootstrapOptions
): readonly EnvironmentChannelService[] {
  let configs: readonly ChannelEnvironmentConfig[]
  try {
    configs = parseChannelEnvironment(options.env ?? process.env)
  } catch {
    options.onStartError?.('environment', '通道环境变量配置无效')
    return []
  }
  const services = configs.map((config) => {
    const driver =
      config.channel === 'dingtalk'
        ? (options.createDingTalkDriver ??
          ((driverOptions) =>
            new DingTalkChannelDriver(driverOptions)))({
            clientId: config.clientId,
            clientSecret: config.clientSecret,
            allowedSenderIds: config.allowedSenderIds,
            ...(options.dingtalkTransportFactory
              ? {
                  transportFactory:
                    options.dingtalkTransportFactory
                }
              : {})
          })
        : (options.createWeComDriver ??
          ((driverOptions) =>
            new WeComChannelDriver(driverOptions)))({
            botId: config.botId,
            secret: config.secret,
            ...(options.wecomTransportFactory
              ? { transportFactory: options.wecomTransportFactory }
              : {})
          })
    const service = (
      options.createService ??
      ((channelDriver, executor, serviceOptions) =>
        new ChannelService(channelDriver, executor, serviceOptions))
    )(driver, options.executor, {
      allowedSenderIds: config.allowedSenderIds,
      allowGroupMessages: config.allowGroupMessages
    })
    void Promise.resolve()
      .then(() => service.start())
      .catch(() => {
        options.onStartError?.(
          config.channel,
          `${config.channel} 通道启动失败`
        )
      })
    return service
  })
  return services
}

export function isReadOnlyChannelMessage(
  message: ChannelInboundText
): boolean {
  return message.workMode === 'ask'
}
