package controllers

import javax.inject._

import models.City
import play.api.Logger
import play.api.libs.json._
import play.api.mvc._
import play.modules.reactivemongo._
import reactivemongo.api.ReadPreference
import reactivemongo.play.json._
import reactivemongo.play.json.collection._
import utils.Errors

import scala.concurrent.{ExecutionContext, Future}


/**
  * Simple controller that directly stores and retrieves [models.City] instances into a MongoDB Collection
  * Input is first converted into a city and then the city is converted to JsObject to be stored in MongoDB
  */
@Singleton
class CityController @Inject()(val reactiveMongoApi: ReactiveMongoApi)(implicit exec: ExecutionContext) extends Controller with MongoController with ReactiveMongoComponents {

  def citiesFuture: Future[JSONCollection] = database.map(_.collection[JSONCollection]("city"))

  def create(name: String, population: Int) = Action.async {
    for {
      cities <- citiesFuture
      lastError <- cities.insert(City(name, population))
    } yield
      Ok("Mongo LastError: %s".format(lastError))
  }

  def createFromJson = Action.async(parse.json) { request =>
    Json.fromJson[City](request.body) match {
      case JsSuccess(city, _) =>
        for {
          cities <- citiesFuture
          lastError <- cities.insert(city)
        } yield {
          Logger.debug(s"Successfully inserted with LastError: $lastError")
          Created("Created 1 city")
        }
      case JsError(errors) =>
        Future.successful(BadRequest("Could not build a city from the json provided. " + Errors.show(errors)))
    }
  }

  def createBulkFromJson = Action.async(parse.json) { request =>
    Json.fromJson[Seq[City]](request.body) match {
      case JsSuccess(newCities, _) =>
        citiesFuture.flatMap { cities =>
          val documents = newCities.map(implicitly[cities.ImplicitlyDocumentProducer](_))

          cities.bulkInsert(ordered = true)(documents: _*).map { multiResult =>
            Logger.debug(s"Successfully inserted with multiResult: $multiResult")
            Created(s"Created ${multiResult.n} cities")
          }
        }
      case JsError(errors) =>
        Future.successful(BadRequest("Could not build a city from the json provided. " + Errors.show(errors)))
    }
  }

  def findByName(name: String) = Action.async {
    // let's do our query
    val futureCitiesList: Future[List[City]] = citiesFuture.flatMap {
      // find all cities with name `name`
      _.find(Json.obj("name" -> name)).
      // perform the query and get a cursor of JsObject
      cursor[City](ReadPreference.primary).
      // Coollect the results as a list
      collect[List]()
    }

    // everything's ok! Let's reply with a JsValue
    futureCitiesList.map { cities =>
      Ok(Json.toJson(cities))
    }
  }
}