package org.aws4s.core

import cats.data.NonEmptyList
import cats.effect.{Effect, Sync}
import io.circe.Json
import org.http4s.{EntityDecoder, MediaRange, Message}
import org.http4s.scalaxml._
import org.http4s.circe._

private[aws4s] sealed trait ResponseContent {
  final def tryParse[A](pf: PartialFunction[ResponseContent, Option[A]]): Option[A] =
    pf.orElse[ResponseContent, Option[A]]({ case _ => None })(this)
}

private[aws4s] case class XmlContent(elem:    scala.xml.Elem) extends ResponseContent
private[aws4s] case class JsonContent(json:   Json) extends ResponseContent
private[aws4s] case class StringContent(text: String) extends ResponseContent
private[aws4s] case object NoContent extends ResponseContent

private[aws4s] object ResponseContent {

  implicit def entityDecoder[F[_]: Effect]: EntityDecoder[F, ResponseContent] =
    EntityDecoder[F, scala.xml.Elem].map(elem => XmlContent(elem)).widen[ResponseContent] orElse
      EntityDecoder[F, Json].map(json         => JsonContent(json)).widen[ResponseContent] orElse
      inclusiveJsonEntityDecoder.map(json     => JsonContent(json)).widen[ResponseContent] orElse
      EntityDecoder[F, String].map(text       => StringContent(text)).widen[ResponseContent] orElse
      EntityDecoder[F, Unit].map(_            => NoContent).widen[ResponseContent]

  private def inclusiveJsonEntityDecoder[F[_]: Sync]: EntityDecoder[F, Json] = {
    val json = jsonDecoder[F]
    val extraMediaRanges = NonEmptyList.of(
      "application/x-amz-json-1.0"
    ) map (mr => MediaRange.parse(mr).getOrElse(throw new RuntimeException(s"Invalid Media Range: $mr")))
    val allMediaRanges = extraMediaRanges concat json.consumes.toList
    EntityDecoder.decodeBy[F, Json](allMediaRanges.head, allMediaRanges.tail: _*)((msg: Message[F]) => json.decode(msg, strict = false))
  }
}