/*
 * Copyright (C) 2020  Prasoon Joshi
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package graphql

import repository.{DataRepository, UserRepository}

import play.api.libs.json.{JsObject, JsString, JsValue, Json}
import play.api.mvc.Results._
import play.api.mvc._
import sangria.execution.{ErrorWithResolver, Executor, QueryAnalysisError}
import sangria.parser.QueryParser

import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
import sangria.marshalling.playJson._
import sangria.schema.Schema
import sangria.execution.ExceptionHandler

case class GraphqlServer(dataRepo: DataRepository,
  userRepo: UserRepository) {

  import scala.concurrent.ExecutionContext.Implicits.global
  val errorHandler = ABagOfGoodies.errorHandler
  def fetchQueryResult(userToken: Option[String],
    requestJson: JsValue)(implicit ec: ExecutionContext): Future[Result] = {

    val JsObject(fields) = requestJson
    val JsString(query) = fields("query")

    QueryParser.parse(query) match {
      case Success(queryAst) =>
        val operation = fields.get("operationName") collect {
          case JsString(op) => op
        }
        val variables = fields.get("variables") match {
          case Some(obj: JsObject) => obj
          case _ => JsObject.empty
        }
        val vars = (requestJson \ "variables").toOption.flatMap {
          case JsString(xs) => Some(parseVariables(xs))
          case obj: JsObject => Some(obj)
          case _ => None //Json.obj()
        }

        executeGraphQLQuery(query = queryAst,
          userToken = userToken,
          errorHandler = errorHandler,
          op = operation,
          vars = {vars getOrElse Json.obj()})

      case Failure(error) =>
        Future.successful(BadRequest(Json.obj("error" -> error.getMessage)))
    }
  }

  def parseVariables(vars: String) =
    if (vars.trim == "" || vars.trim == "null") Json.obj() else Json.parse(vars).as[JsObject]

  def executeGraphQLQuery(query: sangria.ast.Document,
    userToken: Option[String],
    errorHandler: ExceptionHandler,
    op: Option[String], 
    vars: JsObject): Future[Result] =
      Executor.execute(
        schema = SchemaDefinition.schema, 
        query, 
        userContext = 
          new SecureGraphqlContext(userToken, userRepo, dataRepo),
        exceptionHandler = errorHandler,
        operationName = op, 
        variables = vars)
      .map(Ok(_))
      .recover {
        case error: QueryAnalysisError => BadRequest(error.resolveError)
        case error: ErrorWithResolver => InternalServerError(error.resolveError)
      }
}
