const fs = require('fs')
const path = require('path')
const crypto = require('crypto')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')

dayjs.extend(customParseFormat)
dayjs.extend(utc)

jest.mock('axios')

const fixture = JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8'))
const date = dayjs.utc('2026-05-11', 'YYYY-MM-DD').startOf('d')
const channel = {
  site_id: '262270504164',
  xmltv_id: 'Sat1.de'
}

beforeEach(() => {
  jest.resetModules()
  jest.restoreAllMocks()
})

function loadConfig() {
  const axios = require('axios')
  const config = require('./www.magenta.tv.config.js')

  return {
    axios,
    ...config
  }
}

it('can generate valid url', async () => {
  const { axios, url } = loadConfig()

  jest
    .spyOn(crypto, 'randomUUID')
    .mockReturnValueOnce('device-id')
    .mockReturnValueOnce('session-id')
    .mockReturnValueOnce('manifest-call-id')
    .mockReturnValueOnce('schedule-call-id')

  axios.get.mockImplementation(url => {
    if (url === 'https://prod.dcm.telekom-dienste.de/v1/settings/web-mtv/manifest') {
      return Promise.resolve({ data: fixture.manifest })
    }

    return Promise.reject(new Error('unexpected request'))
  })

  const result = await url({ channel, date })

  expect(result).toBe(
    'https://feed.entertainment.tv.theplatform.eu/f/mdeprod/mdeprod-all-channel-schedules?byId=262270504164&byListingTime=2026-05-11T00%3A00%3A00.000Z%7E2026-05-12T00%3A00%3A00.000Z&byLocationId=http%3A%2F%2Fdata.entertainment.tv.theplatform.eu%2Fentertainment%2Fdata%2FLocation%2F245991976396&cid=session-id%3A%3Aschedule-call-id'
  )

  expect(axios.get).toHaveBeenCalledWith(
    'https://prod.dcm.telekom-dienste.de/v1/settings/web-mtv/manifest',
    expect.objectContaining({
      headers: {
        'X-DT-Call-ID': 'manifest-call-id',
        'X-DT-Session-ID': 'session-id'
      },
      params: expect.objectContaining({
        deviceId: 'device-id',
        sid: 'session-id',
        deviceModel: 'WEB2_FTV',
        portal: 'release',
        subscriberType: 'FTV_FREEMIUM_DT',
        $redirect: false
      })
    })
  )
})

it('can map a channel feed entry', () => {
  const { axios, channels } = loadConfig()

  axios.get.mockImplementation((url) => {
    if (url === 'https://prod.dcm.telekom-dienste.de/v1/settings/web-mtv/manifest') {
      return Promise.resolve({ data: fixture.manifest })
    }

    if (new URL(url).searchParams.get('range') === '1-100') {
      return Promise.resolve({ data: fixture.channels })
    }

    return Promise.resolve({ data: { entries: [] } })
  })

  return channels().then(result => {
    expect(result[0]).toMatchObject({
      lang: 'de',
      site_id: '259549736360',
      name: 'Das Erste'
    })
  })
})

it('can ignore radio channels', async () => {
  const { axios, channels } = loadConfig()

  axios.get.mockImplementation((url) => {
    if (url === 'https://prod.dcm.telekom-dienste.de/v1/settings/web-mtv/manifest') {
      return Promise.resolve({ data: fixture.manifest })
    }

    if (new URL(url).searchParams.get('range') === '1-100') {
      return Promise.resolve({ data: fixture.channels })
    }

    return Promise.resolve({ data: { entries: [] } })
  })

  const result = await channels()

  expect(result).toHaveLength(1)
  expect(result[0].name).toBe('Das Erste')
})

it('can paginate channels', async () => {
  const { axios, channels } = loadConfig()

  jest
    .spyOn(crypto, 'randomUUID')
    .mockReturnValueOnce('device-id')
    .mockReturnValueOnce('session-id')
    .mockReturnValueOnce('manifest-call-id')
    .mockImplementation(() => 'call-id')

  const page1 = {
    entries: Array.from({ length: 100 }, (_, index) => ({
      id: `http://data.entertainment.tv.theplatform.eu/entertainment/data/ChannelSchedule/${1000 + index}`,
      title: `Channel ${index + 1}`,
      stations: {
        [`station-${index}`]: {
          title: `Channel ${index + 1}`,
          thumbnails: {
            stationLogo: {
              url: `https://example.com/${index + 1}.png`
            }
          }
        }
      },
      'dt$isRadio': false
    }))
  }
  const page2 = {
    entries: [
      {
        id: 'http://data.entertainment.tv.theplatform.eu/entertainment/data/ChannelSchedule/2001',
        title: 'Channel 101',
        stations: {
          'station-101': {
            title: 'Channel 101',
            thumbnails: {
              stationLogo: {
                url: 'https://example.com/101.png'
              }
            }
          }
        },
        'dt$isRadio': false
      }
    ]
  }

  axios.get.mockImplementation((url, options) => {
    if (url === 'https://prod.dcm.telekom-dienste.de/v1/settings/web-mtv/manifest') {
      return Promise.resolve({ data: fixture.manifest })
    }

    const range = new URL(url).searchParams.get('range')
    if (range === '1-100') return Promise.resolve({ data: page1 })
    if (range === '101-200') return Promise.resolve({ data: page2 })

    return Promise.reject(new Error(`unexpected request ${url} ${JSON.stringify(options)}`))
  })

  const result = await channels()

  expect(result).toHaveLength(101)
  expect(result[0]).toMatchObject({
    site_id: '1000',
    name: 'Channel 1'
  })
  expect(result[100]).toMatchObject({
    site_id: '2001',
    name: 'Channel 101'
  })
})

it('can parse response', async () => {
  const { parser } = loadConfig()
  const result = await parser({ content: JSON.stringify(fixture.schedule), channel })
  const serialized = result.map(program => ({
    ...program,
    start: program.start.toJSON(),
    stop: program.stop.toJSON()
  }))

  expect(serialized).toMatchObject([
    {
      start: '2026-05-11T00:10:00.000Z',
      stop: '2026-05-11T01:07:00.000Z',
      title: 'FBI: Special Crime Unit',
      sub_title: 'Aufstand',
      description:
        'Eine gigantische Explosion in Brooklyn ruft das FBI auf den Plan. Hinweise deuten auf eine vorsätzliche Fremdeinwirkung hin.',
      category: ['200-Serie', 'Krimi', 'Action', 'Thriller'],
      season: 8,
      episode: 7,
      country: 'US',
      date: '2025'
    }
  ])
})

it('can handle empty guide', async () => {
  const { parser } = loadConfig()
  const result = await parser({
    channel,
    content: '{"entries":[{"id":"http://data.entertainment.tv.theplatform.eu/entertainment/data/ChannelSchedule/262270504164","listings":[]}]}'
  })

  expect(result).toMatchObject([])
})

it('can handle listings without program', async () => {
  const { parser } = loadConfig()
  const result = await parser({
    channel,
    content:
      '{"entries":[{"id":"http://data.entertainment.tv.theplatform.eu/entertainment/data/ChannelSchedule/262270504164","listings":[{"startTime":1,"endTime":2}]}]}'
  })

  expect(result).toMatchObject([])
})

it('can build a UTC schedule window', async () => {
  const { axios, url } = loadConfig()

  jest
    .spyOn(crypto, 'randomUUID')
    .mockReturnValueOnce('device-id')
    .mockReturnValueOnce('session-id')
    .mockReturnValueOnce('manifest-call-id')
    .mockReturnValueOnce('schedule-call-id')

  axios.get.mockResolvedValue({ data: fixture.manifest })

  const result = await url({
    channel,
    date
  })

  expect(new URL(result).searchParams.get('byListingTime')).toBe(
    '2026-05-11T00:00:00.000Z~2026-05-12T00:00:00.000Z'
  )
})
