/**
 * @jest-environment node
 */

import { it, expect, beforeAll, afterAll, afterEach, beforeEach, describe, vi, test } from 'vitest'
import { EPGGrabber, EPGGrabberMock } from '../src/index'
import { SiteConfig } from '../src/types/siteConfig'
import * as epgGrabber from '../src/index'
import { http, HttpResponse } from 'msw'
import { pathToFileURL } from 'node:url'
import { setupServer } from 'msw/node'
import path from 'node:path'
import dayjs from 'dayjs'
import fs from 'fs-extra'

describe('EPGGrabber', () => {
  describe('grab()', () => {
    const restHandlers = [
      http.get('http://example.com/20210319/1tv.json', () => {
        return HttpResponse.json([{ title: 'Program1', start: '2021-03-19T04:30:00.000Z' }])
      })
    ]

    const server = setupServer(...restHandlers)

    beforeAll(async () => {
      server.listen({ onUnhandledRequest: 'error' })
    })
    beforeEach(() => vi.useFakeTimers())
    afterAll(() => server.close())
    afterEach(() => server.resetHandlers())

    it('can use global config', async () => {
      const config: SiteConfig = {
        site: 'example.com',
        url: 'http://example.com/20210319/1tv.json',
        parser: ({ content }) => (content ? JSON.parse(content) : [])
      }

      const channel = new epgGrabber.Channel({
        site: 'example.com',
        site_id: '1',
        xmltv_id: '1TV.fr',
        lang: 'fr',
        name: '1TV',
        logo: null,
        url: null,
        lcn: null,
        index: -1
      })

      const grabber = new EPGGrabber(config)
      const promise = grabber.grab(channel, '2022-01-01', (context, error) => {
        if (error) throw error
      })

      vi.advanceTimersByTime(3000)

      const programs = await promise

      expect(programs.length).toBe(1)
      expect(programs[0].toObject()).toMatchObject({
        site: 'example.com',
        channel: '1TV.fr',
        titles: [{ value: 'Program1', lang: 'fr' }],
        subTitles: [],
        descriptions: [],
        icons: [],
        episodeNumbers: [],
        date: 0,
        start: 1616128200000,
        stop: 0,
        urls: [],
        ratings: [],
        categories: [],
        directors: [],
        actors: [],
        writers: [],
        adapters: [],
        audio: {},
        video: {},
        images: [],
        keywords: [],
        languages: [],
        lastChance: [],
        length: [],
        new: false,
        origLanguages: [],
        premiere: [],
        previouslyShown: [],
        reviews: [],
        starRatings: [],
        subtitles: [],
        countries: [],
        producers: [],
        composers: [],
        editors: [],
        presenters: [],
        commentators: [],
        guests: []
      })
    })

    it('can use local configs', async () => {
      const config: SiteConfig = {
        site: 'example.com',
        url: 'http://example.com/20210319/1tv.json',
        parser: ({ content }) => (content ? JSON.parse(content) : [])
      }

      const channel = new epgGrabber.Channel({
        site: 'example.com',
        site_id: '1',
        xmltv_id: '1TV.fr',
        lang: 'fr',
        name: '1TV',
        logo: null,
        url: null,
        lcn: null,
        index: -1
      })

      const grabber = new EPGGrabber()
      const promise = grabber.grab(channel, '2022-01-01', config, (context, error) => {
        if (error) throw error
      })

      vi.advanceTimersByTime(3000)

      const programs = await promise

      expect(programs[0].titles).toMatchObject([
        {
          lang: 'fr',
          value: 'Program1'
        }
      ])
    })
  })

  describe('loadLogo()', () => {
    it('can load logo for channel', async () => {
      const config: SiteConfig = {
        site: 'example.com',
        url: 'http://example.com/20210319/1tv.json',
        parser: ({ content }) => (content ? JSON.parse(content) : []),
        logo: ({ channel }) => `https://example.com/logos/${channel.xmltv_id}`
      }

      const channel = new epgGrabber.Channel({
        site: 'example.com',
        site_id: '1',
        xmltv_id: '1TV.fr',
        lang: 'fr',
        name: '1TV',
        logo: null,
        url: null,
        lcn: null,
        index: -1
      })

      const grabber = new EPGGrabber()
      const logo = await grabber.loadLogo(channel, '2022-01-01', config)

      expect(logo).toBe('https://example.com/logos/1TV.fr')
    })
  })

  describe('parseChannelsXML()', () => {
    it('can parse channels.xml', () => {
      const xml = fs.readFileSync(
        path.resolve(__dirname, './__data__/input/example.channels.xml'),
        'utf-8'
      )
      const channels = EPGGrabber.parseChannelsXML(xml)

      expect(channels.length).toBe(2)
      expect(channels[0]).toBeInstanceOf(epgGrabber.Channel)
      expect(channels[1]).toBeInstanceOf(epgGrabber.Channel)
      expect(channels[0].toObject()).toMatchObject({
        site: 'example.com',
        site_id: '1',
        xmltv_id: '1TV.com',
        lang: 'fr',
        logo: 'https://example.com/logos/1TV.png',
        name: '1 TV',
        index: 0,
        lcn: '36'
      })
      expect(channels[1].toObject()).toMatchObject({
        site: 'example.com',
        site_id: '2',
        lang: null,
        logo: null,
        xmltv_id: '2TV.com',
        name: '2 TV',
        index: 1
      })
    })

    it('can parse channels.xml with inline site attribute', () => {
      const xml = fs.readFileSync(
        path.resolve(__dirname, './__data__/input/example_3.channels.xml'),
        'utf-8'
      )
      const channels = EPGGrabber.parseChannelsXML(xml)

      expect(channels.length).toBe(2)
      expect(channels[0]).toBeInstanceOf(epgGrabber.Channel)
      expect(channels[1]).toBeInstanceOf(epgGrabber.Channel)
      expect(channels[0].toObject()).toMatchObject({
        site: 'example.com',
        site_id: '1',
        xmltv_id: '1TV.com',
        lang: 'fr',
        logo: 'https://example.com/logos/1TV.png',
        name: '1 TV'
      })
      expect(channels[1].toObject()).toMatchObject({
        site: 'example.com',
        site_id: '2',
        lang: null,
        logo: null,
        xmltv_id: '2TV.com',
        name: '2 TV'
      })
    })

    it('can parse legacy channels.xml', () => {
      const xml = fs.readFileSync(
        path.resolve(__dirname, './__data__/input/legacy.channels.xml'),
        'utf-8'
      )
      const channels = EPGGrabber.parseChannelsXML(xml)

      expect(channels.length).toBe(2)
      expect(channels[0]).toBeInstanceOf(epgGrabber.Channel)
      expect(channels[1]).toBeInstanceOf(epgGrabber.Channel)
      expect(channels[0].toObject()).toMatchObject({
        site: 'example.com',
        site_id: '1',
        xmltv_id: '1TV.com',
        lang: 'fr',
        logo: 'https://example.com/logos/1TV.png',
        name: '1 TV'
      })
      expect(channels[1].toObject()).toMatchObject({
        site: 'example.com',
        site_id: '2',
        lang: null,
        logo: null,
        xmltv_id: '2TV.com',
        name: '2 TV'
      })
    })
  })

  describe('generateXMLTV()', () => {
    vi.useFakeTimers().setSystemTime(new Date('2022-05-05'))

    const channels = [
      new epgGrabber.Channel({
        xmltv_id: '1TV.co',
        name: '1 TV',
        site: 'example.com',
        logo: 'https://example.com/channel_one_icon.jpg',
        index: 1,
        url: 'https://example.com/channel_one?foo=foo&bar=bar',
        lcn: '36',
        site_id: '#',
        lang: null
      }),
      new epgGrabber.Channel({
        xmltv_id: '2TV.co',
        name: '2 TV',
        site: 'example.com',
        site_id: '#',
        url: null,
        lcn: null,
        index: 2,
        lang: 'es',
        logo: 'https://example.com/logos/2TV.png'
      }),
      new epgGrabber.Channel({
        xmltv_id: '3TV.co',
        name: '3 TV',
        site: 'example.com',
        site_id: '#',
        url: null,
        lcn: null,
        lang: null,
        logo: null,
        index: 3
      })
    ]

    it('can generate xmltv', () => {
      const programs = [
        new epgGrabber.Program({
          site: 'example.com',
          channel: '1TV.co',
          start: 1616133600000,
          stop: 1616135400000,
          titles: [{ value: 'Program 1' }],
          subTitles: [{ value: 'Sub-title & 1' }],
          descriptions: [{ value: 'Description for Program 1' }],
          date: 1651795200000,
          categories: [{ value: 'Test' }],
          keywords: [
            { lang: 'en', value: 'physical-comedy' },
            { lang: 'en', value: 'romantic' }
          ],
          languages: [{ value: 'English' }],
          origLanguages: [{ lang: 'en', value: 'French' }],
          length: [{ units: 'minutes', value: '60' }],
          urls: [{ value: 'http://example.com/title.html' }],
          countries: [{ value: 'US' }],
          video: {
            present: 'yes',
            colour: 'no',
            aspect: '16:9',
            quality: 'HDTV'
          },
          audio: {
            present: 'yes',
            stereo: 'Dolby Digital'
          },
          episodeNumbers: [
            { system: 'xmltv_ns', value: '8.238.0/1' },
            { system: 'onscreen', value: 'S09E239' }
          ],
          previouslyShown: [{ start: '', channel: '' }],
          premiere: [{ value: 'First time on British TV' }],
          lastChance: [{ lang: 'en', value: 'Last time on this channel' }],
          new: true,
          subtitles: [
            { type: 'teletext', language: [{ value: 'English' }] },
            { type: 'onscreen', language: [{ lang: 'en', value: 'Spanish' }] }
          ],
          ratings: [
            {
              system: 'MPAA',
              value: 'P&G',
              icons: [{ src: 'http://example.com/pg_symbol.png' }]
            }
          ],
          starRatings: [
            {
              system: 'TV Guide',
              value: '4/5',
              icons: [{ src: 'stars.png' }]
            },
            {
              system: 'IMDB',
              value: '8/10',
              icons: []
            }
          ],
          reviews: [
            {
              type: 'text',
              source: 'Rotten Tomatoes',
              reviewer: 'Joe Bloggs',
              lang: 'en',
              value: 'This is a fantastic show!'
            },
            {
              type: 'text',
              source: 'IDMB',
              reviewer: 'Jane Doe',
              lang: 'en',
              value: 'I love this show!'
            },
            {
              type: 'url',
              source: 'Rotten Tomatoes',
              reviewer: 'Joe Bloggs',
              lang: 'en',
              value: 'https://example.com/programme_one_review'
            }
          ],
          directors: [
            {
              value: 'Director 1',
              urls: [{ value: 'http://example.com/director1.html', system: 'TestSystem' }],
              images: [
                { value: 'https://example.com/image1.jpg' },
                {
                  value: 'https://example.com/image2.jpg',
                  type: 'person',
                  size: '2',
                  system: 'TestSystem',
                  orient: 'P'
                }
              ]
            },
            {
              value: 'Director 2',
              urls: [],
              images: []
            }
          ],
          actors: [
            { value: 'Actor 1', urls: [], images: [] },
            { value: 'Actor 2', urls: [], images: [] }
          ],
          writers: [{ value: 'Writer 1', urls: [], images: [] }],
          images: [
            {
              type: 'poster',
              size: '1',
              orient: 'P',
              system: 'tvdb',
              value: 'https://tvdb.com/programme_one_poster_1.jpg?foo=foo&bar=bar'
            },
            {
              type: 'poster',
              size: '2',
              orient: 'P',
              system: 'tmdb',
              value: 'https://tmdb.com/programme_one_poster_2.jpg'
            },
            {
              type: 'backdrop',
              size: '3',
              orient: 'L',
              system: 'tvdb',
              value: 'https://tvdb.com/programme_one_backdrop_3.jpg'
            },
            {
              type: 'backdrop',
              size: '3',
              orient: 'L',
              system: 'tmdb',
              value: 'https://tmdb.com/programme_one_backdrop_3.jpg'
            }
          ],
          icons: [{ src: 'https://example.com/images/Program1.png?x=шеллы&sid=777' }]
        }),
        new epgGrabber.Program({
          site: 'example.com',
          channel: '2TV.co',
          titles: [{ lang: 'es', value: 'Program 2' }],
          start: 1616133600000,
          stop: 1616135400000
        })
      ]

      const output = EPGGrabber.generateXMLTV(channels, programs, { date: dayjs.utc().format('YYYYMMDD') })

      expect(output).toEqual(
    fs.readFileSync(pathToFileURL('tests/__data__/expected/index.guide.xml'), 'utf8')
  )
    })
  })
})

describe('EPGGrabberMock', () => {
  test('grab()', async () => {
    const config: SiteConfig = {
      site: 'example.com',
      url: 'http://example.com/20210319/1tv.json',
      parser: ({ content }) => (content ? JSON.parse(content) : [])
    }

    const channel = new epgGrabber.Channel({
      site: 'example.com',
      site_id: '1',
      xmltv_id: '1TV.fr',
      lang: 'fr',
      name: '1TV',
      logo: null,
      url: null,
      lcn: null,
      index: -1
    })

    const grabber = new EPGGrabberMock(config)
    const programs = await grabber.grab(channel, '2022-01-01', (context, error) => {
      if (error) throw error
    })

    expect(programs.length).toBe(0)
  })
})
