import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
import { tryAtomicMove } from '@server/helpers/fs.js'
import { CONFIG } from '@server/initializers/config.js'
import { remove } from 'fs-extra/esm'
import { existsSync } from 'node:fs'
import { dirname } from 'node:path'
import { spawn } from 'node:child_process'

const lTags = loggerTagsFactory('live-videos', 'preprocessed-video')

export function getLiveVideoBurnedPath (videoPath: string) {
  return `${videoPath}.burned.mp4`
}

export function getLiveVideoSubtitlePath (videoPath: string) {
  if (existsSync(videoPath + '.vtt')) return videoPath + '.vtt'
  if (existsSync(videoPath + '.srt')) return videoPath + '.srt'

  return null
}

export function getLiveVideoPlaybackInputPath (videoPath: string) {
  const burnedPath = getLiveVideoBurnedPath(videoPath)
  return existsSync(burnedPath) ? burnedPath : videoPath
}

export async function syncPreprocessedLiveVideo (videoPath: string) {
  const subtitlePath = getLiveVideoSubtitlePath(videoPath)
  const burnedPath = getLiveVideoBurnedPath(videoPath)

  if (!subtitlePath) {
    await remove(burnedPath).catch(() => undefined)
    return null
  }

  const tmpOutputPath = `${burnedPath}.tmp.mp4`
  await remove(tmpOutputPath).catch(() => undefined)

  const ffmpegPath = CONFIG.LIVE.FFMPEG.PATH?.trim() || 'ffmpeg'
  const subtitleFilterPath = escapeSubtitleFilterPath(subtitlePath)
  const ffmpegArgs = [
    '-y',
    '-hide_banner',
    '-loglevel', 'warning',
    '-i', videoPath,
    '-map', '0:v:0',
    '-map', '0:a?',
    '-vf', `subtitles=${subtitleFilterPath}`,
    '-c:v', 'libx264',
    '-preset', 'veryfast',
    '-c:a', 'aac',
    '-movflags', '+faststart',
    tmpOutputPath
  ]

  await runFFmpeg(ffmpegPath, ffmpegArgs, { videoPath, subtitlePath })
  await remove(burnedPath).catch(() => undefined)
  await tryAtomicMove(tmpOutputPath, burnedPath)

  return burnedPath
}

function escapeSubtitleFilterPath (subtitlePath: string) {
  return subtitlePath
    .replace(/\\/g, '\\\\')
    .replace(/:/g, '\\:')
    .replace(/ /g, '\\ ')
    .replace(/\[/g, '\\[')
    .replace(/\]/g, '\\]')
    .replace(/,/g, '\\,')
    .replace(/'/g, "\\'")
}

function runFFmpeg (ffmpegPath: string, ffmpegArgs: string[], meta: { videoPath: string, subtitlePath: string }) {
  return new Promise<void>((resolve, reject) => {
    const ffmpegProcess = spawn(ffmpegPath, ffmpegArgs, {
      cwd: dirname(meta.videoPath),
      stdio: [ 'ignore', 'ignore', 'pipe' ]
    })

    let stderrBuffer = ''

    ffmpegProcess.stderr.on('data', chunk => {
      const line = chunk?.toString?.() || ''
      stderrBuffer = (stderrBuffer + line).slice(-4_000)
    })

    ffmpegProcess.once('error', err => {
      reject(err)
    })

    ffmpegProcess.once('exit', code => {
      if (code === 0) {
        resolve()
        return
      }

      const stderr = stderrBuffer.trim()

      logger.error('Cannot preprocess live video with burned subtitles.', {
        code,
        stderr,
        ...meta,
        ...lTags()
      })

      reject(new Error(stderr || `ffmpeg exited with code ${code}`))
    })
  })
}
