const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const crypto = require('crypto')

dayjs.extend(utc)
dayjs.extend(timezone)

/**
 * OSN.COM note:
 * - API allows only at max 5 channels, otherwise returns 406
 */

// https://www.osn.com/_next/static/chunks/87cf1e375968671c.js

const cipherName = 'aes-256-cbc'
const cipherKey = Buffer.from('65a04b9b5591f27c837fac433274b494403e0eec5b43698060c28e1162d4460f', 'hex')
const cipherIv = Buffer.from('b7cc0a48d6d023bc1a2a670953ec5622', 'hex')

const tz = dayjs.tz.guess()
const oneDayMs = 864e5
const headers = {}
const caches = {}
let allChannels

module.exports = {
  site: 'osn.com',
  days: 2,
  async url({ date }) {
    return (await getUrlData(date)).url
  },
  request: {
    cache: {
      ttl: 24 * 60 * 60 * 1000 // 1 day
    },
    async headers({ date }) {
      return await getHeaders(date)
    }
  },
  async parser({ content, channel, date }) {
    const programs = []
    content = getDecryptedData(content)
    if (Array.isArray(content?.entries)) {
      // get remaining segments entries
      const cacheId = date.format('YYYYMMDD')
      if (caches[cacheId] === undefined) {
        caches[cacheId] = []
        const segments = await getUrlData(date, false)
        while (segments.length) {
          const segment = segments.shift()
          const result = await axios
            .get(segment.url, { headers: await getHeaders(segment.data) })
            .then(res => getDecryptedData(res.data))
            .catch(err => console.error(`${segment.url}: ${err.message}!`))
          if (Array.isArray(result?.entries)) {
            caches[cacheId].push(...result.entries)
          }
        }
        // add 5s delay to avoid 406
        await new Promise(resolve => setTimeout(resolve, 5000))
      }
      // add entries from cache
      if (Array.isArray(caches[cacheId])) {
        content.entries.push(...caches[cacheId])
      }
      content.entries
        .filter(entry => entry.guid == channel.site_id)
        .forEach(entry => {
          if (Array.isArray(entry.listings)) {
            const listings = entry.listings
              .map(listing => {
                const res = {
                  title: dotProp(listing, `program.titleLocalized.${channel.lang}`),
                  subTitle: dotProp(listing, `program.osnprogram$seriesTitleLocalized.${channel.lang}`),
                  description: dotProp(listing, `program.descriptionLocalized.${channel.lang}`),
                  start: dayjs.tz(listing.startTime, tz),
                  stop: dayjs.tz(listing.endTime, tz),
                  categories: (dotProp(listing, 'program.tags') || [])
                    .map(tag => dotProp(tag, `titleLocalized.${channel.lang}`)),
                  image: dotProp(listing, `program.thumbnails.landscapeTitleImage-${channel.lang}.url`),
                  season: dotProp(listing, 'program.tvSeasonNumber'),
                  episode: dotProp(listing, 'program.tvSeasonEpisodeNumber'),
                  date: dotProp(listing, 'program.year')
                }
                if (res.subTitle) {
                  const subTitle = res.title
                  res.title = res.subTitle
                  res.subTitle = subTitle
                }
                if (res.date) {
                  res.date = `${res.date}`
                }
                return res
              })
            programs.push(...listings)
          }
        })
    }

    return programs
  },
  async channels({ lang = 'ar' }) {
    const channels = []
    const items = await fetchChannels()
    if (Array.isArray(items)) {
      channels.push(...items.map(item => ({
        lang,
        site_id: item.guid,
        name: item.title
      })))
    }

    return channels
  }
}

async function fetchChannels() {
  if (allChannels === undefined) {
    const result = await axios
      .get('https://www.osn.com/apidata/channels?platform=Android', { headers: await getHeaders() })
      .then(res => getDecryptedData(res.data))
      .catch(console.error)

    if (Array.isArray(result)) {
      allChannels = result
    }
  }

  return allChannels
}

async function getHeaders(data) {
  if (dayjs.isDayjs(data)) {
    data = (await getUrlData(data)).data
  }
  const res = { ...headers }
  if (data) {
    res['X-Encrypted-Data'] = getEncryptedData(data)
  }

  return res
}

async function getUrlData(date, first = true) {
  const parts = []
  const startOfDay = date.tz(tz).startOf('d')
  const endOfDay = startOfDay.add(oneDayMs, 'ms')
  const startStop = {
    startTime: startOfDay.valueOf(),
    endTime: endOfDay.valueOf()
  }
  await fetchChannels()
  const segments = await getUrlSegments()
  let i = 0
  for (const v of segments) {
    const data = {
      url: `https://www.osn.com/apidata/tv-schedule-timeline?t=batch${++i}-time${
          startStop.startTime
        }-${
          startStop.endTime
        }-boxAndroid`,
      data: {
        channelGuid: v.join('|'),
        ...startStop
      }
    }
    if (first) {
      return data
    } else if (i === 1) {
      continue
    }
    parts.push(data)
  }

  return parts
}

async function getUrlSegments() {
  await fetchChannels()
  const segments = []
  const _channels = [...allChannels.map(item => item.guid)]
  while (_channels.length) {
    segments.push(_channels.splice(0, 5))
  }
  return segments
}

function getEncryptedData(data) {
  return encrypt(JSON.stringify(data))
}

function getDecryptedData(data) {
  if (typeof data === 'string' || Buffer.isBuffer(data)) {
    data = JSON.parse(data)
  }
  if (data?.encrypted) {
    data = JSON.parse(decrypt(data.encrypted))
  }

  return data
}

function encrypt(data, encoding = 'utf8') {
  const cipher = crypto.createCipheriv(cipherName, cipherKey, cipherIv)
  const encrypted = cipher.update(data, encoding, 'base64')

  return (encrypted + cipher.final('base64'))
}

function decrypt(data, encoding = 'utf8') {
  const decipher = crypto.createDecipheriv(cipherName, cipherKey, cipherIv)
  const decrypted = decipher.update(data, 'base64', encoding)

  return (decrypted + decipher.final(encoding))
}

function dotProp(o, prop) {
  if (typeof o === 'object') {
    const props = prop.split('.')
    while (props.length) {
      const k = props.shift()
      if (o[k] !== undefined) {
        o = o[k]
      } else {
        break
      }
    }
    if (props.length === 0) {
      return o
    }
  }
}
