// SPDX-FileCopyrightText: 2024-2025 John Livingston <https://www.john-livingston.fr/>
//
// SPDX-License-Identifier: AGPL-3.0-only

import type { RegisterServerOptions, VideoObject, SettingValue } from '@peertube/peertube-types'
import type {
  LiveChatVideoObject,
  VideoBuildResultContext,
  LiveChatJSONLDLink,
  LiveChatJSONLDAttribute,
  PeertubeXMPPServerInfos
} from './types'
import { storeVideoLiveChatInfos } from './storage'
import { videoHasWebchat } from '../../../shared/lib/video'
import { getBoshUri, getWSUri, getWSS2SUri, getPublicChatUri } from '../uri/webchat'
import { canonicalizePluginUri } from '../uri/canonicalize'
import { getProsodyDomain } from '../prosody/config/domain'
import { fillVideoCustomFields } from '../custom-fields'
import { Emojis } from '../emojis'
import { loc } from '../loc'
import { isDebugMode } from '../debug'

/**
 * This function adds LiveChat information on video ActivityPub data if relevant.
 * @param options server options
 * @param videoJsonld JSON-LD video data to fill
 * @param context handler context
 * @returns void
 */
async function videoBuildJSONLD (
  options: RegisterServerOptions,
  videoJsonld: VideoObject | LiveChatVideoObject,
  context: VideoBuildResultContext
): Promise<VideoObject | LiveChatVideoObject> {
  const logger = options.peertubeHelpers.logger
  const video = context.video
  if (video.remote) { return videoJsonld } // should not happen, but... just in case...

  const settings = await options.settingsManager.getSettings([
    'chat-per-live-video',
    'chat-all-lives',
    'chat-all-non-lives',
    'chat-videos-list',
    'disable-websocket',
    'prosody-room-type',
    'federation-dont-publish-remotely',
    'chat-no-anonymous',
    'prosody-room-allow-s2s',
    'prosody-s2s-port'
  ])

  if (settings['federation-dont-publish-remotely']) {
    // Note: we store also outgoing data. Could help for migration/cleanup scripts, for example.
    await storeVideoLiveChatInfos(options, video, false)
    return videoJsonld
  }

  await fillVideoCustomFields(options, video)
  const hasChat = videoHasWebchat({
    'chat-per-live-video': !!settings['chat-per-live-video'],
    'chat-all-lives': !!settings['chat-all-lives'],
    'chat-all-non-lives': !!settings['chat-all-non-lives'],
    'chat-videos-list': settings['chat-videos-list'] as string
  }, video)

  if (!hasChat) {
    logger.debug(`Video uuid=${video.uuid} has not livechat, adding peertubeLiveChat=false.`)
    // Note: we store also outgoing data. Could help for migration/cleanup scripts, for example.
    await storeVideoLiveChatInfos(options, video, false)
    return videoJsonld
  }

  logger.debug(`Adding LiveChat data on video uuid=${video.uuid}...`)

  const prosodyDomain = await getProsodyDomain(options)
  let roomJID: string
  if (settings['prosody-room-type'] === 'channel') {
    roomJID = `channel.${video.channelId}@room.${prosodyDomain}`
  } else {
    roomJID = `${video.uuid}@room.${prosodyDomain}`
  }

  const serverInfos = await _serverBuildInfos(options, {
    'federation-dont-publish-remotely': settings['federation-dont-publish-remotely'],
    'prosody-s2s-port': settings['prosody-s2s-port'],
    'prosody-room-allow-s2s': settings['prosody-room-allow-s2s'],
    'disable-websocket': settings['disable-websocket'],
    'chat-no-anonymous': settings['chat-no-anonymous']
  })

  const chatTitle = loc('chat_for_live_stream') + ' ' + video.name

  // Adding attachments, as described in FEP-1970
  const discussionLinks: LiveChatVideoObject['attachment'] = []
  discussionLinks.push({
    type: 'Link',
    name: chatTitle,
    rel: 'discussion',
    href: getPublicChatUri(options, videoJsonld)
  })
  // Adding the xmpp:// link requires:
  // - prosody-room-allow-s2s
  // - prosody-s2s-port
  // For now, this can be tested reading serverInfos.directs2s
  // There is a debug_mode flag to always enable it.
  if (!!serverInfos.directs2s || isDebugMode(options, 'alwaysPublishXMPPRoom')) {
    discussionLinks.push({
      type: 'Link',
      name: chatTitle,
      rel: 'discussion',
      href: 'xmpp://' + roomJID + '?join'
    })
  }

  if (!('attachment' in videoJsonld) || !videoJsonld.attachment) {
    Object.assign(videoJsonld, {
      attachment: discussionLinks
    })
  } else if (Array.isArray(videoJsonld.attachment)) {
    videoJsonld.attachment.push(...discussionLinks)
  } else {
    videoJsonld.attachment = [
      videoJsonld.attachment,
      ...discussionLinks
    ]
  }

  // Code beneath this point is for backward compatibility, before v7.2.0.
  // Since then, the ActivityPub metadata were not standardized.
  // Note: plugin version >=7.2.0 still uses these data to get remote server informations
  //       (not 100% sure if it is needed or not)

  // For backward compatibility with remote servers, using plugin <=6.3.0, we must provide links:
  const links: LiveChatJSONLDLink[] = []
  if (serverInfos.anonymous) {
    if (serverInfos.anonymous.bosh) {
      links.push({
        type: 'xmpp-bosh-anonymous',
        url: serverInfos.anonymous.bosh,
        jid: serverInfos.anonymous.virtualhost
      })
    }
    if (serverInfos.anonymous.websocket) {
      links.push({
        type: 'xmpp-websocket-anonymous',
        url: serverInfos.anonymous.websocket,
        jid: serverInfos.anonymous.virtualhost
      })
    }
  }

  // This is the custom format used by plugin > 6.3.0.
  const peertubeLiveChat: LiveChatJSONLDAttribute = {
    type: 'xmpp',
    jid: roomJID,
    links,
    xmppserver: serverInfos,
    customEmojisUrl: await Emojis.singletonSafe()?.channelCustomEmojisUrl(video.channelId)
  }
  Object.assign(videoJsonld, {
    peertubeLiveChat
  })
  // Note: we store also outgoing data. Could help for migration/cleanup scripts, for example.
  await storeVideoLiveChatInfos(options, video, peertubeLiveChat)
  return videoJsonld
}

async function videoContextBuildJSONLD (_options: RegisterServerOptions, jsonld: any[]): Promise<any> {
  // Note: this function is called for all kind of context, not only video.
  // We have no parameter to know on which context we currently are.
  // See: https://github.com/Chocobozzz/PeerTube/issues/6375
  // But we only want to add some context on videos...
  // So, to detect if we are on video, we search for a 'isLiveBroadcast' field in jsonld (this only exists for videos).

  const entry = jsonld.find(e => typeof e === 'object' && ('isLiveBroadcast' in e))
  if (!entry) {
    return jsonld
  }

  // We are on a video!
  return jsonld.concat([{
    ptlc: 'urn:peertube-plugin-livechat',
    peertubeLiveChat: {
      '@id': 'ptlc:peertubeLiveChat', '@type': '@json'
    }
  }])
}

async function serverBuildInfos (options: RegisterServerOptions): Promise<PeertubeXMPPServerInfos> {
  const settings = await options.settingsManager.getSettings([
    'federation-dont-publish-remotely',
    'prosody-s2s-port',
    'prosody-room-allow-s2s',
    'disable-websocket',
    'chat-no-anonymous'
  ])
  return _serverBuildInfos(options, {
    'federation-dont-publish-remotely': settings['federation-dont-publish-remotely'],
    'prosody-s2s-port': settings['prosody-s2s-port'],
    'prosody-room-allow-s2s': settings['prosody-room-allow-s2s'],
    'disable-websocket': settings['disable-websocket'],
    'chat-no-anonymous': settings['chat-no-anonymous']
  })
}

async function _serverBuildInfos (
  options: RegisterServerOptions,
  settings: {
    'federation-dont-publish-remotely': SettingValue
    'prosody-s2s-port': SettingValue
    'prosody-room-allow-s2s': SettingValue
    'disable-websocket': SettingValue
    'chat-no-anonymous': SettingValue
  }
): Promise<PeertubeXMPPServerInfos> {
  const prosodyDomain = await getProsodyDomain(options)
  const mucDomain = 'room.' + prosodyDomain
  const anonDomain = 'anon.' + prosodyDomain
  const externalDomain = 'external.' + prosodyDomain

  let directs2s: PeertubeXMPPServerInfos['directs2s'] | undefined
  if (settings['prosody-room-allow-s2s'] && settings['prosody-s2s-port']) {
    directs2s = {
      port: (settings['prosody-s2s-port'] as string) ?? ''
    }
  }

  let websockets2s: PeertubeXMPPServerInfos['websockets2s'] | undefined
  if (!settings['federation-dont-publish-remotely']) {
    const wsS2SUri = getWSS2SUri(options)
    if (wsS2SUri) { // can be undefined for old Peertube version that dont allow WS for plugins
      websockets2s = {
        url: canonicalizePluginUri(options, wsS2SUri, {
          removePluginVersion: true,
          protocol: 'ws'
        })
      }
    }
  }

  let anonymous: PeertubeXMPPServerInfos['anonymous'] | undefined
  if (!settings['chat-no-anonymous']) {
    anonymous = {
      bosh: canonicalizePluginUri(options, getBoshUri(options), { removePluginVersion: true }),
      virtualhost: anonDomain
    }

    if (!settings['disable-websocket']) {
      const wsUri = getWSUri(options)
      if (wsUri) {
        anonymous.websocket = canonicalizePluginUri(options, wsUri, {
          removePluginVersion: true,
          protocol: 'ws'
        })
      }
    }
  }

  return {
    host: prosodyDomain,
    muc: mucDomain,
    external: externalDomain, // we will always add it, even if disabled. Can't cause trouble.
    directs2s,
    websockets2s,
    anonymous
  }
}

export {
  videoBuildJSONLD,
  videoContextBuildJSONLD,
  serverBuildInfos
}
