/*
 * Copyright 2021 - 2023 Sporta Technologies PVT LTD & the ZIO HTTP contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package zio.http

import scala.util.Try

import zio.Trace
import zio.stacktracer.TracingImplicits.disableAutoTrace

sealed trait Status extends Product with Serializable { self =>

  def isInformational: Boolean = code >= 100 && code < 200
  def isSuccess: Boolean       = code >= 200 && code < 300
  def isRedirection: Boolean   = code >= 300 && code < 400
  def isClientError: Boolean   = code >= 400 && code < 500
  def isServerError: Boolean   = code >= 500 && code < 600
  def isError: Boolean         = isClientError | isServerError

  /**
   * Returns the status code.
   */
  val code: Int

  val reasonPhrase: String =
    self.productPrefix.flatMap { c => if (c.isUpper) s" $c" else s"$c" }.drop(1)

  lazy val text: String = code.toString

  /**
   * Returns a Routes[Any, Nothing] that responses with this http status code.
   */
  def toRoutes(implicit trace: Trace): Routes[Any, Nothing] =
    Handler.status(self).toRoutes

  /**
   * Returns a Response with empty data and no headers.
   */
  def toResponse: Response = Response.status(self)
}

object Status {
  sealed trait Informational extends Status // 100 – 199
  sealed trait Success       extends Status // 200 – 299
  sealed trait Redirection   extends Status // 300 – 399
  sealed trait Error         extends Status // 400 – 599
  sealed trait ClientError   extends Error  // 400 – 499
  sealed trait ServerError   extends Error  // 500 – 599

  case object Continue extends Informational { override val code: Int = 100 }

  case object SwitchingProtocols extends Informational { override val code: Int = 101 }

  case object Processing extends Informational { override val code: Int = 102 }

  case object Ok extends Success { override val code: Int = 200 }

  case object Created extends Success { override val code: Int = 201 }

  case object Accepted extends Success { override val code: Int = 202 }

  case object NonAuthoritativeInformation extends Success { override val code: Int = 203 }

  case object NoContent extends Success { override val code: Int = 204 }

  case object ResetContent extends Success { override val code: Int = 205 }

  case object PartialContent extends Success { override val code: Int = 206 }

  case object MultiStatus extends Success { override val code: Int = 207 }

  case object MultipleChoices extends Redirection { override val code: Int = 300 }

  case object MovedPermanently extends Redirection { override val code: Int = 301 }

  case object Found extends Redirection { override val code: Int = 302 }

  case object SeeOther extends Redirection { override val code: Int = 303 }

  case object NotModified extends Redirection { override val code: Int = 304 }

  case object UseProxy extends Redirection { override val code: Int = 305 }

  case object TemporaryRedirect extends Redirection { override val code: Int = 307 }

  case object PermanentRedirect extends Redirection { override val code: Int = 308 }

  case object BadRequest extends ClientError { override val code: Int = 400 }

  case object Unauthorized extends ClientError { override val code: Int = 401 }

  case object PaymentRequired extends ClientError { override val code: Int = 402 }

  case object Forbidden extends ClientError { override val code: Int = 403 }

  case object NotFound extends ClientError { override val code: Int = 404 }

  case object MethodNotAllowed extends ClientError { override val code: Int = 405 }

  case object NotAcceptable extends ClientError { override val code: Int = 406 }

  case object ProxyAuthenticationRequired extends ClientError { override val code: Int = 407 }

  case object RequestTimeout extends ClientError { override val code: Int = 408 }

  case object Conflict extends ClientError { override val code: Int = 409 }

  case object Gone extends ClientError { override val code: Int = 410 }

  case object LengthRequired extends ClientError { override val code: Int = 411 }

  case object PreconditionFailed extends ClientError { override val code: Int = 412 }

  case object RequestEntityTooLarge extends ClientError { override val code: Int = 413 }

  case object RequestUriTooLong extends ClientError { override val code: Int = 414 }

  case object UnsupportedMediaType extends ClientError { override val code: Int = 415 }

  case object RequestedRangeNotSatisfiable extends ClientError { override val code: Int = 416 }

  case object ExpectationFailed extends ClientError { override val code: Int = 417 }

  case object MisdirectedRequest extends ClientError { override val code: Int = 421 }

  case object UnprocessableEntity extends ClientError { override val code: Int = 422 }

  case object Locked extends ClientError { override val code: Int = 423 }

  case object FailedDependency extends ClientError { override val code: Int = 424 }

  case object UnorderedCollection extends ClientError { override val code: Int = 425 }

  case object UpgradeRequired extends ClientError { override val code: Int = 426 }

  case object PreconditionRequired extends ClientError { override val code: Int = 428 }

  case object TooManyRequests extends ClientError { override val code: Int = 429 }

  case object RequestHeaderFieldsTooLarge extends ClientError { override val code: Int = 431 }

  case object InternalServerError extends ServerError { override val code: Int = 500 }

  case object NotImplemented extends ServerError { override val code: Int = 501 }

  case object BadGateway extends ServerError { override val code: Int = 502 }

  case object ServiceUnavailable extends ServerError { override val code: Int = 503 }

  case object GatewayTimeout extends ServerError { override val code: Int = 504 }

  case object HttpVersionNotSupported extends ServerError { override val code: Int = 505 }

  case object VariantAlsoNegotiates extends ServerError { override val code: Int = 506 }

  case object InsufficientStorage extends ServerError { override val code: Int = 507 }

  case object NotExtended extends ServerError { override val code: Int = 510 }

  case object NetworkAuthenticationRequired extends ServerError { override val code: Int = 511 }

  final case class Custom(override val code: Int, override val reasonPhrase: String = "") extends Status

  def fromString(code: String): Option[Status] =
    Try(code.toInt).toOption.map(fromInt)

  def fromInt(code: Int): Status = {
    (code: @annotation.switch) match {
      case 100 => Status.Continue
      case 101 => Status.SwitchingProtocols
      case 102 => Status.Processing
      case 200 => Status.Ok
      case 201 => Status.Created
      case 202 => Status.Accepted
      case 203 => Status.NonAuthoritativeInformation
      case 204 => Status.NoContent
      case 205 => Status.ResetContent
      case 206 => Status.PartialContent
      case 207 => Status.MultiStatus
      case 300 => Status.MultipleChoices
      case 301 => Status.MovedPermanently
      case 302 => Status.Found
      case 303 => Status.SeeOther
      case 304 => Status.NotModified
      case 305 => Status.UseProxy
      case 307 => Status.TemporaryRedirect
      case 308 => Status.PermanentRedirect
      case 400 => Status.BadRequest
      case 401 => Status.Unauthorized
      case 402 => Status.PaymentRequired
      case 403 => Status.Forbidden
      case 404 => Status.NotFound
      case 405 => Status.MethodNotAllowed
      case 406 => Status.NotAcceptable
      case 407 => Status.ProxyAuthenticationRequired
      case 408 => Status.RequestTimeout
      case 409 => Status.Conflict
      case 410 => Status.Gone
      case 411 => Status.LengthRequired
      case 412 => Status.PreconditionFailed
      case 413 => Status.RequestEntityTooLarge
      case 414 => Status.RequestUriTooLong
      case 415 => Status.UnsupportedMediaType
      case 416 => Status.RequestedRangeNotSatisfiable
      case 417 => Status.ExpectationFailed
      case 421 => Status.MisdirectedRequest
      case 422 => Status.UnprocessableEntity
      case 423 => Status.Locked
      case 424 => Status.FailedDependency
      case 425 => Status.UnorderedCollection
      case 426 => Status.UpgradeRequired
      case 428 => Status.PreconditionRequired
      case 429 => Status.TooManyRequests
      case 431 => Status.RequestHeaderFieldsTooLarge
      case 500 => Status.InternalServerError
      case 501 => Status.NotImplemented
      case 502 => Status.BadGateway
      case 503 => Status.ServiceUnavailable
      case 504 => Status.GatewayTimeout
      case 505 => Status.HttpVersionNotSupported
      case 506 => Status.VariantAlsoNegotiates
      case 507 => Status.InsufficientStorage
      case 510 => Status.NotExtended
      case 511 => Status.NetworkAuthenticationRequired
      case _   => Status.Custom(code, "")
    }
  }
}
