package io.udash
package rest

import com.avsystem.commons.meta.MacroInstances
import com.avsystem.commons.meta.MacroInstances.materializeWith
import io.udash.rest.openapi.OpenApiMetadata
import io.udash.rest.raw.RawRest.FromTask
import io.udash.rest.raw.{RawRest, RestMetadata}
import monix.eval.TaskLike

trait ClientInstances[Real] {
  def asReal: RawRest.AsRealRpc[Real]
  def metadata: RestMetadata[Real]
}
trait ServerInstances[Real] {
  def asRaw: RawRest.AsRawRpc[Real]
  def metadata: RestMetadata[Real]
}
trait FullInstances[Real] extends ServerInstances[Real] with ClientInstances[Real]

trait OpenApiInstances[Real] {
  def openapiMetadata: OpenApiMetadata[Real]
}
trait OpenApiServerInstances[Real] extends ServerInstances[Real] with OpenApiInstances[Real]
trait OpenApiFullInstances[Real] extends FullInstances[Real] with OpenApiInstances[Real]

trait ServerImplInstances[Real] {
  @materializeWith(RawRest, "materializeApiAsRaw")
  def asRaw: RawRest.AsRawRpc[Real]
  @materializeWith(RestMetadata, "materializeForImpl")
  def metadata: RestMetadata[Real]
}
trait OpenApiImplInstances[Real] {
  @materializeWith(OpenApiMetadata, "materializeForImpl")
  def openapiMetadata: OpenApiMetadata[Real]
}
trait OpenApiServerImplInstances[Real] extends ServerImplInstances[Real] with OpenApiImplInstances[Real]

/** @see [[io.udash.rest.RestApiCompanion RestApiCompanion]] */
abstract class RestClientApiCompanion[Implicits, Real](protected val implicits: Implicits)(
  implicit inst: MacroInstances[Implicits, ClientInstances[Real]]
) {
  implicit final lazy val restMetadata: RestMetadata[Real] = inst(implicits, this).metadata
  implicit final lazy val restAsReal: RawRest.AsRealRpc[Real] = inst(implicits, this).asReal

  final def fromHandleRequest(handleRequest: RawRest.HandleRequest): Real =
    RawRest.fromHandleRequest(handleRequest)
}

/** @see [[io.udash.rest.RestApiCompanion RestApiCompanion]] */
abstract class RestServerApiCompanion[Implicits, Real](protected val implicits: Implicits)(
  implicit inst: MacroInstances[Implicits, ServerInstances[Real]]
) {
  implicit final lazy val restMetadata: RestMetadata[Real] = inst(implicits, this).metadata
  implicit final lazy val restAsRaw: RawRest.AsRawRpc[Real] = inst(implicits, this).asRaw

  final def asHandleRequest(real: Real): RawRest.HandleRequest =
    RawRest.asHandleRequest(real)
}

/** @see [[io.udash.rest.RestApiCompanion RestApiCompanion]] */
abstract class RestServerOpenApiCompanion[Implicits, Real](protected val implicits: Implicits)(
  implicit inst: MacroInstances[Implicits, OpenApiServerInstances[Real]]
) {
  implicit final lazy val restMetadata: RestMetadata[Real] = inst(implicits, this).metadata
  implicit final lazy val restAsRaw: RawRest.AsRawRpc[Real] = inst(implicits, this).asRaw
  implicit final lazy val openapiMetadata: OpenApiMetadata[Real] = inst(implicits, this).openapiMetadata

  final def asHandleRequest(real: Real): RawRest.HandleRequest =
    RawRest.asHandleRequest(real)
}

/**
 * Base class for REST trait companions. Reduces boilerplate needed in order to define appropriate instances
 * of `AsRawReal` and `RestMetadata` for given trait. The `Implicits` type parameter lets you inject additional implicits
 * into macro materialization of these instances, e.g. [[io.udash.rest.DefaultRestImplicits DefaultRestImplicits]].
 * Usually, for even less boilerplate, this base class is extended by yet another abstract class which fixes
 * the `Implicits` type, e.g. [[io.udash.rest.DefaultRestApiCompanion DefaultRestApiCompanion]].
 */
abstract class RestApiCompanion[Implicits, Real](protected val implicits: Implicits)(
  implicit inst: MacroInstances[Implicits, FullInstances[Real]]
) {
  implicit final lazy val restMetadata: RestMetadata[Real] = inst(implicits, this).metadata
  implicit final lazy val restAsRaw: RawRest.AsRawRpc[Real] = inst(implicits, this).asRaw
  implicit final lazy val restAsReal: RawRest.AsRealRpc[Real] = inst(implicits, this).asReal

  final def fromHandleRequest(handleRequest: RawRest.HandleRequest): Real =
    RawRest.fromHandleRequest(handleRequest)
  final def asHandleRequest(real: Real): RawRest.HandleRequest =
    RawRest.asHandleRequest(real)
}

/** @see [[io.udash.rest.RestApiCompanion RestApiCompanion]] */
abstract class RestOpenApiCompanion[Implicits, Real](protected val implicits: Implicits)(
  implicit inst: MacroInstances[Implicits, OpenApiFullInstances[Real]]
) {
  implicit final lazy val restMetadata: RestMetadata[Real] = inst(implicits, this).metadata
  implicit final lazy val restAsRaw: RawRest.AsRawRpc[Real] = inst(implicits, this).asRaw
  implicit final lazy val restAsReal: RawRest.AsRealRpc[Real] = inst(implicits, this).asReal
  implicit final lazy val openapiMetadata: OpenApiMetadata[Real] = inst(implicits, this).openapiMetadata

  final def fromHandleRequest(handleRequest: RawRest.HandleRequest): Real =
    RawRest.fromHandleRequest(handleRequest)
  final def asHandleRequest(real: Real): RawRest.HandleRequest =
    RawRest.asHandleRequest(real)
}

trait PolyRestApiFullInstances[T[_[_]]] {
  def asRawRest[F[_] : TaskLike]: RawRest.AsRawRpc[T[F]]
  def fromRawRest[F[_] : FromTask]: RawRest.AsRealRpc[T[F]]
  def restMetadata[F[_] : TaskLike]: RestMetadata[T[F]]
  def openapiMetadata[F[_] : TaskLike]: OpenApiMetadata[T[F]]
}

abstract class DefaultPolyRestApiCompanion[T[_[_]]](implicit
  instances: MacroInstances[GenCodecRestImplicits, PolyRestApiFullInstances[T]]
) {
  private lazy val inst = instances(GenCodecRestImplicits, this)
  implicit def asRawRest[F[_] : TaskLike]: RawRest.AsRawRpc[T[F]] = inst.asRawRest
  implicit def fromRawRest[F[_] : FromTask]: RawRest.AsRealRpc[T[F]] = inst.fromRawRest
  implicit def restMetadata[F[_] : TaskLike]: RestMetadata[T[F]] = inst.restMetadata
  implicit def openapiMetadata[F[_] : TaskLike]: OpenApiMetadata[T[F]] = inst.openapiMetadata
}

/**
 * Like [[RestServerApiCompanion]] but the `Real` type is supposed to be a class with REST methods
 * already implemented - as opposed to a trait with abstract methods that needs separate implementation.
 * All public methods of this class - abstract or concrete - will be interpreted as REST methods
 * (unless annotated with [[com.avsystem.commons.meta.ignore ignore]]).
 */
abstract class RestServerApiImplCompanion[Implicits, Real](protected val implicits: Implicits)(
  implicit inst: MacroInstances[Implicits, ServerImplInstances[Real]]
) {
  implicit final lazy val restMetadata: RestMetadata[Real] = inst(implicits, this).metadata
  implicit final lazy val restAsRaw: RawRest.AsRawRpc[Real] = inst(implicits, this).asRaw

  final def asHandleRequest(real: Real): RawRest.HandleRequest =
    RawRest.asHandleRequest(real)
}

/**
 * Like [[RestServerApiImplCompanion]] but additionally materializes `OpenApiMetadata` for the class.
 */
abstract class RestServerOpenApiImplCompanion[Implicits, Real](protected val implicits: Implicits)(
  implicit inst: MacroInstances[Implicits, OpenApiServerImplInstances[Real]]
) {
  implicit final lazy val restMetadata: RestMetadata[Real] = inst(implicits, this).metadata
  implicit final lazy val restAsRaw: RawRest.AsRawRpc[Real] = inst(implicits, this).asRaw
  implicit final lazy val openapiMetadata: OpenApiMetadata[Real] = inst(implicits, this).openapiMetadata

  final def asHandleRequest(real: Real): RawRest.HandleRequest =
    RawRest.asHandleRequest(real)
}
