import httpclient, asyncdispatch, times, sequtils, strutils, json, uri
import macros, options

import ".."/[types, parser, formatters, cache]
import utils, consts

var
  guestToken = ""
  tokenUses = 0
  tokenMaxUses = 230
  tokenUpdated: Time
  tokenLifetime = initDuration(minutes=20)

macro genMediaGet(media: untyped; token=false) =
  let
    mediaName = capitalizeAscii($media)
    multi = ident("get" & mediaName & "s")
    convo = ident("getConversation" & mediaName & "s")
    single = ident("get" & mediaName)

  quote do:
    proc `multi`*(thread: Chain | Timeline; agent: string; token="") {.async.} =
      if thread == nil: return
      var `media` = thread.content.filterIt(it.`media`.isSome)
      when `token`:
        var gToken = token
        if gToken.len == 0: gToken = await getGuestToken(agent)
        await all(`media`.mapIt(`single`(it, token, agent)))
      else:
        await all(`media`.mapIt(`single`(it, agent)))

    proc `convo`*(convo: Conversation; agent: string) {.async.} =
      var futs: seq[Future[void]]
      when `token`:
        var token = await getGuestToken(agent)
        futs.add `single`(convo.tweet, agent, token)
        futs.add `multi`(convo.before, agent, token=token)
        futs.add `multi`(convo.after, agent, token=token)
        if convo.replies != nil:
          futs.add convo.replies.content.mapIt(`multi`(it, agent, token=token))
      else:
        futs.add `single`(convo.tweet, agent)
        futs.add `multi`(convo.before, agent)
        futs.add `multi`(convo.after, agent)
        if convo.replies != nil:
          futs.add convo.replies.content.mapIt(`multi`(it, agent))
      await all(futs)

proc getGuestToken(agent: string; force=false): Future[string] {.async.} =
  if getTime() - tokenUpdated < tokenLifetime and
     not force and tokenUses < tokenMaxUses:
    return guestToken

  tokenUpdated = getTime()
  tokenUses = 0

  let headers = genHeaders({"authorization": auth}, agent, base, lang=false)
  newClient()

  var res: string
  try: res = await client.postContent($(apiBase / tokenUrl))
  except: return

  let json = parseJson(res)

  if json != nil:
    result = json["guest_token"].to(string)
    guestToken = result

proc getVideoVar(tweet: Tweet): var Option[Video] =
  if tweet.card.isSome():
    return get(tweet.card).video
  else:
    return tweet.video

proc getVideoFetch(tweet: Tweet; agent, token: string; retry=true): Future[Option[Video]] {.async.} =
  if tweet.video.isNone(): return

  let
    headers = genHeaders({"authorization": auth, "x-guest-token": token},
                         agent, base / getLink(tweet, focus=false), lang=false)
    url = apiBase / (videoUrl % $tweet.id)
    json = await fetchJson(url, headers)

  if json == nil:
    if not retry: return
    if getTime() - tokenUpdated > initDuration(seconds=1):
      tokenUpdated = getTime()
      discard await getGuestToken(agent, force=true)
    return await getVideoFetch(tweet, agent, guestToken, retry=false)

  var video = parseVideo(json, tweet.id)
  video.title = get(tweet.video).title
  video.description = get(tweet.video).description
  cache(video)

  result = some video
  tokenUses.inc

proc videoIsInvalid(video: Video): bool =
  not video.available and video.url.len == 0

proc getVideo*(tweet: Tweet; agent, token: string; force=false) {.async.} =
  let token = if token.len == 0: guestToken else: token
  var video = getCachedVideo(tweet.id)
  if video.isNone:
    video = await getVideoFetch(tweet, agent, token)
  elif videoIsInvalid(get(video)) and tweet.gif.isSome:
    # gif was mistakenly parsed as a gif
    uncache(tweet.id)
    return

  getVideoVar(tweet) = video
  if tweet.card.isSome: tweet.video = none Video

proc getPoll*(tweet: Tweet; agent: string) {.async.} =
  if tweet.poll.isNone(): return

  let
    headers = genHeaders(agent, base / getLink(tweet, focus=false), auth=true)
    url = base / (pollUrl % $tweet.id)
    html = await fetchHtml(url, headers)

  if html == nil: return
  tweet.poll = some parsePoll(html)

proc getCard*(tweet: Tweet; agent: string) {.async.} =
  if tweet.card.isNone(): return

  let
    headers = genHeaders(agent, base / getLink(tweet, focus=false), auth=true)
    query = get(tweet.card).query.replace("sensitive=true", "sensitive=false")
    html = await fetchHtml(base / query, headers)

  if html == nil: return
  parseCard(get(tweet.card), html)

proc getPhotoRail*(username, agent: string; skip=false): Future[seq[GalleryPhoto]] {.async.} =
  if skip: return
  let
    headers = genHeaders(agent, base / username, xml=true)
    params = {"for_photo_rail": "true", "oldest_unread_id": "0"}
    url = base / (timelineMediaUrl % username) ? params
    html = await fetchHtml(url, headers, jsonKey="items_html")

  result = parsePhotoRail(html)

genMediaGet(video, token=true)
genMediaGet(poll)
genMediaGet(card)
