import { LooseObject } from "../../../types/looseTypes"
import { StreamerData, StreamData, Social } from "../../../types/scraping/Streamer"

const base64 = (data: String) => {
  return Buffer.from(data).toString('base64url')
}

/**
 * Class that interacts with the Twitch api
 */
export class TwitchAPI {
    public readonly twitchUrl = 'https://gql.twitch.tv/gql'
    public headers = {
        "Client-Id": "kimne78kx3ncx6brgo4mv6wki5h1ko"
    }

    constructor() {}

    /**
     * Gets information about a streamer, like socials, about, and more.
     * @see StreamerData
     * @param streamerName The username of the streamer
     * @returns Promise<StreamerData>
     */
    public getStreamerInfo = async (streamerName: string) => {
        const payload = [
            {
                "operationName": "ChannelRoot_AboutPanel",
                "variables": {
                    "channelLogin": streamerName,
                    "skipSchedule": false
                },
                "extensions": {
                    "persistedQuery": {
                        "version": 1,
                        "sha256Hash": "6089531acef6c09ece01b440c41978f4c8dc60cb4fa0124c9a9d3f896709b6c6"
                    }
                }
            },
            {
                "operationName":"StreamMetadata",
                "variables":{
                    "channelLogin": streamerName
                },
                "extensions":{
                    "persistedQuery":{
                        "version":1,
                        "sha256Hash":"a647c2a13599e5991e175155f798ca7f1ecddde73f7f341f39009c14dbf59962"
                    }
                }
            },
            {
                "operationName": "StreamTagsTrackingChannel",
                "variables": {
                  "channel": streamerName
                },
                "extensions": {
                  "persistedQuery": {
                    "version": 1,
                    "sha256Hash": "6aa3851aaaf88c320d514eb173563d430b28ed70fdaaf7eeef6ed4b812f48608"
                  }
                }
            },
            {
                "operationName": "VideoPreviewOverlay",
                "variables": {
                  "login": streamerName
                },
                "extensions": {
                  "persistedQuery": {
                    "version": 1,
                    "sha256Hash": "9515480dee68a77e667cb19de634739d33f243572b007e98e67184b1a5d8369f"
                  }
                }
              },
              {
                "operationName": "UseViewCount",
                "variables": {
                  "channelLogin": streamerName
                },
                "extensions": {
                  "persistedQuery": {
                    "version": 1,
                    "sha256Hash": "00b11c9c428f79ae228f30080a06ffd8226a1f068d6f52fbc057cbde66e994c2"
                  }
                }
              },
        ]

        const res = await fetch(this.twitchUrl, {
            method: 'POST',
            body: JSON.stringify(payload),
            headers: this.headers
        })

        const data = await res.json()
        const rawStreamerData = data[0].data


        // get socials
        const socials: LooseObject[] = []
        if (rawStreamerData.user.channel && rawStreamerData.user.channel.socialMedias) {
            for (let social of rawStreamerData.user.channel.socialMedias) {
                socials.push({
                    type: social.name,
                    name: social.title,
                    link: social.url
                })
            }
        }

        // check if is liver
        const rawStreamData = data[1].data.user.stream
        let parsedStream: StreamData | null;
        if(!rawStreamData) {
            parsedStream = null
        } else {
            const tags: string[] = []
            for (let tagData of data[2].data.user.stream.freeformTags) {
                tags.push(tagData.name)
            } 

            const previewUrl = `${process.env.URL}/proxy/img/${base64(data[3].data.user.stream.previewImageURL)}`

            parsedStream = {
                title: data[1].data.user.lastBroadcast.title,
                topic: rawStreamData.game.name,
                startedAt: new Date(rawStreamData.createdAt).valueOf(),
                tags,
                viewers: Number(data[4].data.user.stream.viewersCount),
                preview: data[3].data.user.stream.previewImageURL
            }
        }

        const abbreviatedFollowers = Intl.NumberFormat('en-US', {
          notation: "compact",
          maximumFractionDigits: 1
        }).format(rawStreamerData.user.followers.totalCount)

        const streamerData: StreamerData = {
            username: rawStreamerData.user.displayName,
            about: rawStreamerData.user.description,
            pfp: rawStreamerData.user.profileImageURL,
            followers: rawStreamerData.user.followers.totalCount,
            socials: socials as Social[],
            isLive: (!!parsedStream),
            isPartner: rawStreamerData.user.isPartner,
            followersAbbv: abbreviatedFollowers,
            colorHex: '#' + rawStreamerData.user.primaryColorHex,
            id: Number(rawStreamerData.user.id),
            stream: parsedStream
        }
        
        return Promise.resolve(streamerData)
    }

    /**
     * Gets the current viewers of a stream
     * @param streamerName The username of the streamer
     * @returns Promise<number>
     */
    public getViewers = async (streamerName: string) => {
        const payload = [
            {
                "operationName": "UseViewCount",
                "variables": {
                  "channelLogin": streamerName
                },
                "extensions": {
                  "persistedQuery": {
                    "version": 1,
                    "sha256Hash": "00b11c9c428f79ae228f30080a06ffd8226a1f068d6f52fbc057cbde66e994c2"
                  }
                }
              },
        ]

        const res = await fetch(this.twitchUrl, {
            method: 'POST',
            body: JSON.stringify(payload),
            headers: this.headers
        })

        const rawData = await res.json()

        if(!rawData[0].data.user.stream)
            return Promise.reject(new Error(`Streamer ${streamerName} is not live`))

        return Promise.resolve(rawData[0].data.user.stream.viewersCount)
    }

    public isLive = async (streamerName: string) => {
      const payload = [
          {
              "operationName": "UseViewCount",
              "variables": {
                "channelLogin": streamerName
              },
              "extensions": {
                "persistedQuery": {
                  "version": 1,
                  "sha256Hash": "00b11c9c428f79ae228f30080a06ffd8226a1f068d6f52fbc057cbde66e994c2"
                }
              }
            },
      ]

      const res = await fetch(this.twitchUrl, {
          method: 'POST',
          body: JSON.stringify(payload),
          headers: this.headers
      })

      const rawData = await res.json()

      if(!rawData[0].data.user.stream)
          return Promise.resolve(false)

      return Promise.resolve(true)
    }

    /**
     * Gets the stream playlist file from twitch
     * @param streamerName The username of the streamer
     * @returns Promise<boolean>
     */
    public getStream = async (streamerName: string) => {
      const isLive = await this.isLive(streamerName)
      if(!isLive) return Promise.reject(new Error(`Streamer ${streamerName} is not live`))

      // Get token
      const payload = {
        "operationName": "PlaybackAccessToken_Template",
        "query": "query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) {  streamPlaybackAccessToken(channelName: $login, params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: $isLive) {    value    signature    __typename  }  videoPlaybackAccessToken(id: $vodID, params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: $isVod) {    value    signature    __typename  }}",
        "variables": {
          "isLive": true,
          "login": streamerName,
          "isVod": false,
          "vodID": "",
          "playerType": "site"
        }
      }

      var res = await fetch(this.twitchUrl, {
        headers: this.headers,
        body: JSON.stringify(payload),
        method: 'POST'
      })

      const data = await res.json()

      const token = data.data.streamPlaybackAccessToken.value
      const signature = data.data.streamPlaybackAccessToken.signature

      const playlistUrl = `https://usher.ttvnw.net/api/channel/hls/${streamerName.toLowerCase()}.m3u8`
      const params = `?sig=${signature}&token=${token}`
      var res = await fetch(playlistUrl + params, {
        headers: this.headers
      })

      return await res.text()
    }


    /**
     * Gets the homepage discovery tab of twitch
     * @param limit Maximum categories to get
     * @param cursor The current page you're at (for pagination)

     */
    public getDirectory = async (limit: number, cursor?: number) => {
      const payload: any[] = [
        {
          "operationName": "BrowsePage_AllDirectories",
          "variables": {
            "limit": limit,
            "options": {
              "recommendationsContext": {
                "platform": "web"
              },
              "requestID": "JIRA-VXP-2397",
              "sort": "RELEVANCE",
              "tags": []
            }
          },
          "extensions": {
            "persistedQuery": {
              "version": 1,
              "sha256Hash": "1d1914ca3cbfaa607ecd5595b2e305e96acf987c8f25328f7713b25f604c4668"
            }
          }
        }
      ]

      if(cursor) 
        payload[0].variables.cursor = cursor

      const res = await fetch(this.twitchUrl, {
          method: 'POST',
          body: JSON.stringify(payload),
          headers: this.headers
      })
      const data = await res.json()
      const categories = data[0].data.directoriesWithTags.edges
      let formattedCategories = []

      for (let category of categories) {
        let tags = []
        for (let tag of category.node.tags) {
          tags.push(tag.tagName)
        }

        formattedCategories.push({
          name: category.node.name,
          displayName: category.node.displayName,
          viewers: category.node.viewersCount,
          tags: tags,
          createdAt: category.node.originalReleaseDate,
          image: `${process.env.URL}/proxy/img/${base64(category.node.avatarURL)}`
        })
      }

      return formattedCategories
    }

    public getDirectoryGame = async (name: string, streamLimit: number) => {
      const payload: any[] = [
        {
          "operationName": "DirectoryPage_Game",
          "variables": {
            "imageWidth": 50,
            "name": name,
            "options": {
              "sort": "RELEVANCE",
              "recommendationsContext": {
                "platform": "web"
              },
              "requestID": "JIRA-VXP-2397",
              "freeformTags": null,
              "tags": []
            },
            "sortTypeIsRecency": false,
            "limit": streamLimit
          },
          "extensions": {
            "persistedQuery": {
              "version": 1,
              "sha256Hash": "df4bb6cc45055237bfaf3ead608bbafb79815c7100b6ee126719fac3762ddf8b"
            }
          }
        },
        {
          "operationName": "Directory_DirectoryBanner",
          "variables": {
            "name": name
          },
          "extensions": {
            "persistedQuery": {
              "version": 1,
              "sha256Hash": "2670fbecd8fbea0211c56528d6eff5752ef9d6c73cd5238d395784b46335ded4"
            }
          }
        },
      ]

      const res = await fetch(this.twitchUrl, {
          method: 'POST',
          body: JSON.stringify(payload),
          headers: this.headers
      })
      const data = await res.json()

      if(!data[0].data.game) return null;


      let streams = []
      if(data[0].data.game.streams)
        streams = data[0].data.game.streams.edges
      let formatedStreams = []

      for(let stream of streams) {
        let tags = []
        for (let tag of stream.node.freeformTags) {
          tags.push(tag.name)
        }

        formatedStreams.push({
          title: stream.node.title,
          viewers: stream.node.viewersCount,
          preview: stream.node.previewImageURL,
          tags,
          streamer: {
            name: stream.node.broadcaster.displayName,
            pfp: stream.node.broadcaster.profileImageURL,
            colorHex: stream.node.broadcaster.primaryColorHex
          }
        })
      }
      
      const rawGameData = data[1].data.game;
      let tags = []
      for(let tag of rawGameData.tags) {
        tags.push(tag.tagName)
      }

      const formatedGameData = {
        name: rawGameData.name,
        cover: rawGameData.avatarURL,
        description: rawGameData.description,
        viewers: rawGameData.viewersCount,
        followers: rawGameData.followersCount,
        tags,
        streams: formatedStreams
      }

      
      return formatedGameData
    }
}