package ru.yandex.atom.frontend

import ru.yandex.atom.service.{APIKeysActorComponent, AuthenticatorComponent}
import spray.routing.{RequestContext, ExceptionHandler, Directive1, HttpServiceActor}
import akka.actor.Props
import scala.concurrent.ExecutionContext.Implicits.global
import directive.CommonDirectives._
import akka.util.Timeout
import scala.concurrent.duration._
import language.postfixOps
import ru.yandex.atom.utils.actor.AtomActorLogging
import spray.httpx.marshalling.ToResponseMarshaller
import spray.routing.directives.RespondWithDirectives._
import spray.httpx.SprayJsonSupport
import ru.yandex.atom.frontend.error.{Rejection, JsonErrorMarshalling}
import spray.http.{RemoteAddress, StatusCodes}
import ru.yandex.atom.error.{ErrorUtils, InternalException, UserException}
import ru.yandex.atom.data.ReqID
import ru.yandex.atom.application.HttpRouterComponent
import ru.yandex.atom.frontend.data.marshallers.JsonMarshalling._
import JsonErrorMarshalling._
import ru.yandex.atom.frontend.unmarshall.Unmarshalling._

/**
 * @author avhaliullin
 */
trait ViewerRouterComponent extends HttpRouterComponent {
  self: AuthenticatorComponent
    with ViewerBackendProxyComponent
    with APIKeysActorComponent =>

  class ViewerRouter extends HttpServiceActor with SprayJsonSupport with AtomActorLogging {
    def errorHandler(id: ReqID, m: ToResponseMarshaller[Throwable]): ExceptionHandler = ExceptionHandler {
      case err: InternalException => complete(err)
      case err: UserException => complete(err)
      case err =>
        logWithId.error(id, err, "Request processing failed")
        complete(err)
    }

    def auth(id: ReqID, ip: RemoteAddress): Directive1[Long] = authenticate(authenticator.auth(id, ip, _ : RequestContext))

    implicit val timeout = Timeout(5 seconds)

    var _reqId = 0L

    def genId() = {
      _reqId += 1
      ReqID(self.path.name, _reqId.toString)
    }

    val route = {
      (path("ping") & pathEndOrSingleSlash) {
        complete("atom-api.search.yandex.net")
      } ~
      clientIP {
        remoteIp =>
          (logReq(logWithId, genId, remoteIp) & decompressRequest() & compressResponseIfRequested() & handleRejections(Rejection.Handler)) {
            reqId =>
            // Передаю marshaller для exception-ов явно, потому что implicit lookup в этом месте создает путаницу
              (pathPrefix("rest") & handleExceptions(errorHandler(reqId, JsonErrorMarshalling.exceptionMarshaller))) {
                auth(reqId, remoteIp) {
                  userId =>
                    pathPrefix("url-lists") {
                      pathEnd {
                        (get & complete) {
                          viewerBackendProxy.listUrlListIds(reqId, userId)
                        } ~
                          (put & entity(as[Set[String]])) {
                            urls =>
                              (respondWithStatus(StatusCodes.Created) & complete) {
                                viewerBackendProxy.createUrlList(reqId, userId, urls)
                              }
                          }
                      } ~
                        (path(LongNumber) & pathEnd) {
                          id =>
                            get {
                              complete {
                                viewerBackendProxy.readUrlsList(reqId, userId, id)
                              }
                            } ~
                              post {
                                entity(as[Set[String]]) {
                                  urls =>
                                    complete {
                                      viewerBackendProxy.updateUrlList(reqId, userId, id, urls).map(_ => StatusCodes.NoContent)
                                    }
                                }
                              }
                        }
                    }
                }
              } ~
              (path("internal" / "create-key" / LongNumber) & get){
                uid =>
                  complete{
                    import akka.pattern.ask
                    ErrorUtils.safeMapFutureMessage[APIKeysResponse, APIKeysResponse.KeyCreated](apiKeysActor ? APIKeysRequest.CreateKey(reqId, uid)).map(_.key)
                  }
              }
          }
      }
    }

    def receive = runRoute(route)
  }

  object ViewerRouter {
    def props = Props(classOf[ViewerRouter], self)
  }

}
