const dayjs = require('dayjs')
const doFetch = require('@ntlab/sfetch')
const debug = require('debug')('site:tv.yandex.ru')

doFetch
  .setDebugger(debug)
  .setMaxWorker(10)

// enable to fetch guide description but its take a longer time
const detailedGuide = true

// update this data by heading to https://tv.yandex.ru and change the values accordingly
const cookies = {
  i: 'eIUfSP+/mzQWXcH+Cuz8o1vY+D2K8fhBd6Sj0xvbPZeO4l3cY+BvMp8fFIuM17l6UE1Z5+R2a18lP00ex9iYVJ+VT+c=',
  spravka: 'dD0xNzM0MjA0NjM4O2k9MTI1LjE2NC4xNDkuMjAwO0Q9QTVCQ0IyOTI5RDQxNkU5NkEyOTcwMTNDMzZGMDAzNjRDNTFFNDM4QkE2Q0IyOTJDRjhCOTZDRDIzODdBQzk2MzRFRDc5QTk2Qjc2OEI1MUY5MTM5M0QzNkY3OEQ2OUY3OTUwNkQ3RjBCOEJGOEJDMjAwMTQ0RDUwRkFCMDNEQzJFMDI2OEI5OTk5OUJBNEFERUYwOEQ1MjUwQTE0QTI3RDU1MEQwM0U0O3U9MTczNDIwNDYzODUyNDYyNzg1NDtoPTIxNTc0ZTc2MDQ1ZjcwMDBkYmY0NTVkM2Q2ZWMyM2Y1',
  yandexuid: '1197179041732383499',
  yashr: '4682342911732383504',
  yuidss: '1197179041732383499',
  user_display: 824,
}
const headers = {
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 OPR/114.0.0.0',
}
const caches = {}

module.exports = {
  site: 'tv.yandex.ru',
  days: 2,
  url({ date }) {
    return getUrl(date)
  },
  request: {
    cache: {
      ttl: 3600000 // 1 hour
    },
    headers: getHeaders()
  },
  async parser({ content, date, channel }) {
    const programs = []
    const events = []

    if (content && parseContent(content, date, true)) {
      const cacheid = date.format('YYYY-MM-DD')
      if (!caches[cacheid]) {
        debug(`Please wait while fetching schedules for ${cacheid}`)
        caches[cacheid] = await fetchSchedules({ date, content })
      }
      if (detailedGuide) {
        await fetchPrograms({ schedules: caches[cacheid], date, channel })
      }
      caches[cacheid].forEach(schedule => {
        schedule.events
          .filter(event => event.channelFamilyId == channel.site_id && date.isSame(event.start, 'day'))
          .forEach(event => {
            if (events.indexOf(event.id) < 0) {
              events.push(event.id)
              programs.push({
                title: event.title,
                description: event.program.description,
                category: event.program.type.name,
                start: dayjs(event.start),
                stop: dayjs(event.finish)
              })
            }
          })
      })
    }

    return programs
  },
  async channels() {
    const channels = []
    const included = []
    const schedules = await fetchSchedules({ date: dayjs() })
    schedules.forEach(schedule => {
      if (schedule.channel && included.indexOf(schedule.channel.familyId) < 0) {
        included.push(schedule.channel.familyId)
        channels.push({
          lang: 'ru',
          site_id: schedule.channel.familyId.toString(),
          name: schedule.channel.title
        })
      }
    })

    return channels
  }
}

async function fetchSchedules({ date, content = null }) {
  const schedules = []
  const queues = []
  const fetches = []
  const url = getUrl(date)

  let mainApi
  // parse content as schedules and add to queue if more requests is needed
  const f = (src, res, headers) => {
    if (src) {
      fetches.push(src)
    }
    if (headers) {
      parseCookies(headers)
    }
    const [q, s] = parseContent(res, date)
    if (!mainApi) {
      mainApi = true
      if (caches.region) {
        queues.push(getQueue(getUrl(date, caches.region), src))
      }
    }
    for (const url of q) {
      if (fetches.indexOf(url) < 0) {
        queues.push(getQueue(url, src))
      }
    }
    schedules.push(...s)
  }
  // is main html already fetched?
  if (content) {
    f(url, content)
  } else {
    queues.push(getQueue(url, 'https://tv.yandex.ru/'))
  }
  // fetch all queues
  await doFetch(queues, f)

  return schedules
}

async function fetchPrograms({ schedules, date, channel }) {
  const queues = []
  schedules
    .filter(schedule => schedule.channel.familyId == channel.site_id)
    .forEach(schedule => {
      queues.push(
        ...schedule.events
          .filter(event => date.isSame(event.start, 'day'))
          .map(event => getQueue(getUrl(null, caches.region, null, event), 'https://tv.yandex.ru/'))
      )
    })
  await doFetch(queues, (queue, res, headers) => {
    if (headers) {
      parseCookies(headers)
    }
    // is it a program?
    if (res?.program) {
      let updated = false
      schedules.forEach(schedule => {
        schedule.events.forEach(event => {
          if (event.channelFamilyId === res.channelFamilyId && event.id === res.id) {
            Object.assign(event, res)
            updated = true
            return true
          }
        })
        if (updated) {
          return true
        }
      })
    }
  })
}

function parseContent(content, date, checkOnly = false) {
  const queues = []
  const schedules = []
  let valid = false
  if (content) {
    if (Buffer.isBuffer(content)) {
      content = content.toString()
    }
    // got captcha, its look like our cookies has expired
    if (content?.type === 'captcha' || (typeof content === 'string' && content.match(/SmartCaptcha/))) {
      throw new Error('Got captcha, please goto https://tv.yandex.ru and update cookies!')
    }
    if (typeof content === 'object') {
      let items
      if (content.schedule) {
        // fetch next request based on schedule map
        if (Array.isArray(content.schedule.scheduleMap)) {
          queues.push(...content.schedule.scheduleMap.map(m => getUrl(date, caches.region, m)))
        }
        // find some schedules?
        if (Array.isArray(content.schedule.schedules)) {
          items = content.schedule.schedules
        }
      }
      // find another schedules?
      if (Array.isArray(content.schedules)) {
        items = content.schedules
      }
      // add programs
      if (items && items.length) {
        schedules.push(...getSchedules(items))
      }
    } else {
      // prepare headers for next http request
      const [, region] = content.match(/region: '(\d+)'/i) || [null, null]
      const [, initialSk] = content.match(/window.__INITIAL_SK__ = (.*);/i) || [null, null]
      const [, sessionId] = content.match(/window.__USER_SESSION_ID__ = "(.*)";/i) || [null, null]
      const tvSk = initialSk ? JSON.parse(initialSk) : {}
      if (region) {
        caches.region = region
      }
      if (tvSk.key) {
        headers['X-Tv-Sk'] = tvSk.key
      }
      if (sessionId) {
        headers['X-User-Session-Id'] = sessionId
      }
      if (checkOnly && region && tvSk.key && sessionId) {
        valid = true;
      }
    }
  }

  return checkOnly ? valid :  [queues, schedules]
}

function parseCookies(headers) {
  if (Array.isArray(headers['set-cookie'])) {
    headers['set-cookie']
      .forEach(cookie => {
        const [key, value] = cookie.split('; ')[0].split('=')
        if (cookies[key] !== value) {
          cookies[key] = value
          debug(`Update cookie ${key}=${value}`)
        }
      })
  }
}

function getSchedules(schedules) {
  return schedules.filter(schedule => schedule.events.length);
}

function getHeaders(data = {}) {
  return Object.assign({}, headers, {
    Cookie: Object.keys(cookies).map(cookie => `${cookie}=${cookies[cookie]}`).join('; ')
  }, data)
}

function getUrl(date, region = null, page = null, event = null) {
  let url = 'https://tv.yandex.ru/'
  if (region) {
    url += `api/${region}`
  }
  if (page && page.id !== undefined) {
    url += `${url.endsWith('/') ? '' : '/'}main/chunk?page=${page.id}`
  }
  if (event && event.id !== undefined) {
    url += `${url.endsWith('/') ? '' : '/'}event?eventId=${event.id}&programCoId=`
  }
  if (date) {
    url += `${url.indexOf('?') < 0 ? '?' : '&'}date=${date.format('YYYY-MM-DD')}${!page ? '&grid=all' : ''}&period=all-day`
  }
  if (page && page.id !== undefined && page.offset !== undefined) {
    url += `${url.indexOf('?') < 0 ? '?' : '&'}offset=${page.offset}`
  }
  if (page && page.id !== undefined && page.limit !== undefined) {
    url += `${url.indexOf('?') < 0 ? '?' : '&'}limit=${page.limit}`
  }
  return url
}

function getQueue(url, referer) {
  const data = {
    'Origin': 'https://tv.yandex.ru',
  }
  if (referer) {
    data['Referer'] = referer
  }
  if (url.indexOf('api') > 0) {
    data['X-Requested-With'] = 'XMLHttpRequest'
  }
  const headers = getHeaders(data)
  return {
    url,
    params: { headers }
  }
}