/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */

import { HttpStatusCode } from '@peertube/peertube-models'
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
import { makeGetRequest, makeRawRequest, PeerTubeServer } from '@peertube/peertube-server-commands'
import { expect } from 'chai'
import { pathExists } from 'fs-extra/esm'
import { readFile } from 'fs/promises'
import { join, parse } from 'path'

// Default interval -> 5 minutes
function dateIsValid (dateString: string | Date, interval = 300000) {
  const dateToCheck = new Date(dateString)
  const now = new Date()

  return Math.abs(now.getTime() - dateToCheck.getTime()) <= interval
}

function expectStartWith (str: string, start: string) {
  expect(str.startsWith(start), `${str} does not start with ${start}`).to.be.true
}

function expectNotStartWith (str: string, start: string) {
  expect(str.startsWith(start), `${str} does not start with ${start}`).to.be.false
}

function expectEndWith (str: string, end: string) {
  expect(str.endsWith(end), `${str} does not end with ${end}`).to.be.true
}

// ---------------------------------------------------------------------------

async function expectLogDoesNotContain (server: PeerTubeServer, str: string) {
  const content = await server.servers.getLogContent()

  expect(content.toString()).to.not.contain(str)
}

async function expectLogContain (server: PeerTubeServer, str: string) {
  const content = await server.servers.getLogContent()

  expect(content.toString()).to.contain(str)
}

async function testAvatarSize (options: {
  url: string
  imageName: string
  avatar: {
    width: number
    path: string
  }
}) {
  const { url, imageName, avatar } = options

  const { body } = await makeGetRequest({
    url,
    path: avatar.path,
    expectedStatus: HttpStatusCode.OK_200
  })

  const extension = parse(avatar.path).ext
  // We don't test big GIF avatars
  if (extension === '.gif' && avatar.width > 150) return

  const fixturePath = buildAbsoluteFixturePath(imageName + extension)
  const data = await readFile(fixturePath)
  const minLength = data.length - ((40 * data.length) / 100)
  const maxLength = data.length + ((40 * data.length) / 100)

  expect(body.length).to.be.above(minLength, 'the generated image is way smaller than the recorded fixture ' + fixturePath)
  expect(body.length).to.be.below(maxLength, 'the generated image is way larger than the recorded fixture ' + fixturePath)
}

async function testImageGeneratedByFFmpeg (url: string, imageName: string, imageHTTPPath: string, extension = '.jpg') {
  if (process.env.ENABLE_FFMPEG_THUMBNAIL_PIXEL_COMPARISON_TESTS !== 'true') {
    console.log(
      'Pixel comparison of image generated by ffmpeg is disabled. ' +
        'You can enable it using `ENABLE_FFMPEG_THUMBNAIL_PIXEL_COMPARISON_TESTS=true env variable'
    )
    return
  }

  return testImage({ url: url + imageHTTPPath, name: `${imageName}${extension}` })
}

async function testImage (options: {
  name: string
  url: string
}) {
  const { name, url } = options

  const { body } = await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 })
  const data = await readFile(buildAbsoluteFixturePath(name))

  const { PNG } = await import('pngjs')
  const JPEG = await import('jpeg-js')
  const pixelmatch = (await import('pixelmatch')).default

  const img1 = url.endsWith('.png')
    ? PNG.sync.read(body)
    : JPEG.decode(body)

  const img2 = name.endsWith('.png')
    ? PNG.sync.read(data)
    : JPEG.decode(data)

  const errorMsg = `${url} image is not the same as ${name}`

  try {
    const result = pixelmatch(img1.data, img2.data, null, img1.width, img1.height, { threshold: 0.1 })

    expect(result).to.equal(0, errorMsg)
  } catch (err) {
    throw new Error(`${errorMsg}: ${err.message}`)
  }
}

async function testFileExistsOnFSOrNot (server: PeerTubeServer, directory: string, filePath: string, exist: boolean) {
  const base = server.servers.buildDirectory(directory)

  expect(await pathExists(join(base, filePath))).to.equal(exist)
}

// ---------------------------------------------------------------------------

function checkBadStartPagination (url: string, path: string, token?: string, query = {}) {
  return makeGetRequest({
    url,
    path,
    token,
    query: { ...query, start: 'hello' },
    expectedStatus: HttpStatusCode.BAD_REQUEST_400
  })
}

async function checkBadCountPagination (url: string, path: string, token?: string, query = {}) {
  await makeGetRequest({
    url,
    path,
    token,
    query: { ...query, count: 'hello' },
    expectedStatus: HttpStatusCode.BAD_REQUEST_400
  })

  await makeGetRequest({
    url,
    path,
    token,
    query: { ...query, count: 2000 },
    expectedStatus: HttpStatusCode.BAD_REQUEST_400
  })
}

function checkBadSortPagination (url: string, path: string, token?: string, query = {}) {
  return makeGetRequest({
    url,
    path,
    token,
    query: { ...query, sort: 'hello' },
    expectedStatus: HttpStatusCode.BAD_REQUEST_400
  })
}

// ---------------------------------------------------------------------------

async function checkVideoDuration (server: PeerTubeServer, videoUUID: string, duration: number) {
  const video = await server.videos.get({ id: videoUUID })

  expect(video.duration).to.be.approximately(duration, 1)

  for (const file of video.files) {
    const metadata = await server.videos.getFileMetadata({ url: file.metadataUrl })

    for (const stream of metadata.streams) {
      expect(Math.round(stream.duration)).to.be.approximately(duration, 1)
    }
  }
}

export {
  checkBadCountPagination,
  checkBadSortPagination,
  checkBadStartPagination,
  checkVideoDuration,
  dateIsValid,
  expectEndWith,
  expectLogContain,
  expectLogDoesNotContain,
  expectNotStartWith,
  expectStartWith,
  testAvatarSize,
  testFileExistsOnFSOrNot,
  testImage,
  testImageGeneratedByFFmpeg
}
