package org.aws4s

import cats.Show
import cats.implicits._
import org.aws4s.core._
import org.http4s.{Headers, Status}

abstract class Failure(message: String) extends RuntimeException(message) {
  override def toString: String = this.show
}

case class ErrorResponse(status: Status, errorType: String, code: String, message: String)
    extends Failure(
      s"""
     |Error response:
     |  status:   $status,
     |  type:     $errorType
     |  code:     $code
     |  message:  $message
   """.stripMargin
    )

case class UnexpectedResponse(content: String) extends Failure(s"Unexpected response: $content")

case class InvalidParam(content: String) extends Failure(s"Invalid param: $content")

case class InvalidCommand(content: String) extends Failure(s"Invalid command: $content")

object Failure {

  implicit val showFailure: Show[Failure] = Show.show(err => s"aws4s failure: ${err.getMessage}")

  /** Tries to parse the given XML nodes as an error response */
  private[aws4s] def tryErrorResponse(status: Status, elem: xml.Elem): Option[Failure] =
    if (elem.label == "ErrorResponse")
      Some(
        ErrorResponse(
          status,
          (elem \ "Error" \ "Type").text,
          (elem \ "Error" \ "Code").text,
          (elem \ "Error" \ "Message").text
        )
      )
    else if (elem.label == "Error")
      Some(
        ErrorResponse(
          status,
          "",
          (elem \ "Code").text,
          (elem \ "Message").text
        )
      )
    else
      None

  private[aws4s] def unexpectedResponse(content: String): Failure = UnexpectedResponse(content)

  private[aws4s] def badResponse(status: Status, headers: Headers, responseContent: ResponseContent): Failure = {
    def preambled(strBody: String) = status.show |+| "\n" |+| headers.show |+| "\n\n" |+| strBody
    responseContent match {
      case XmlContent(elem)    => tryErrorResponse(status, elem).getOrElse(unexpectedResponse(preambled(elem.toString)))
      case JsonContent(json)   => unexpectedResponse(json.spaces2)
      case StringContent(text) => unexpectedResponse(preambled(text))
      case NoContent           => unexpectedResponse(preambled("[No content]"))
    }
  }

  private[aws4s] def invalidParam(paramName: String, cause: String): Failure = InvalidParam(s"$paramName is invalid because $cause")

  private[aws4s] def invalidCommand(cause: String): Failure = InvalidCommand(cause)

  private implicit val showStatus: Show[Status] = Show.fromToString
}