/*
 * 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 repository

import repository.NiosxGraph._
import models.{
  NiosxUser, AuthPayload
}
import utils.{FileUtils, SecurityUtils}
import graphql.SchemaInputObjects.UserInput

import play.api.libs.ws.WSClient
import play.api.Configuration
import scala.util.Try
import scala.collection.mutable

sealed trait UserRole
object UserRole {
  val PartnerPermissions = 
    "VIEW_USER" :: "EDIT_ENTITY" :: "VIEW_ENTITY" :: "EDIT_ANNO" :: "VIEW_ANNO" :: Nil
  val PatronPermissions = 
    "VIEW_USER" :: "EDIT_ANNO" :: "VIEW_ANNO" :: Nil
  val AdminPermissions = PartnerPermissions ++ PatronPermissions ++ 
  ("EDIT_USER" :: "VIEW_USER" :: "SUPER_USER" :: Nil)

  def getUserPermissions(r: String): List[String] = getRole(r) match {
    case NiosxAdminRole => AdminPermissions
    case NiosxPartnerRole => PartnerPermissions
    case NiosxPatronRole => PatronPermissions
  }
  def getRole(r: String): UserRole = r match {
    case "admin" => NiosxAdminRole
    case "partner" => NiosxPartnerRole
    case "patron" => NiosxPatronRole
  }
  def roleToString(r: UserRole): String = r match {
    case NiosxAdminRole => "admin"
    case NiosxPartnerRole => "partner"
    case NiosxPatronRole => "patron"
  }
}
case object NiosxAdminRole extends UserRole
case object NiosxPartnerRole extends UserRole
case object NiosxPatronRole extends UserRole

class SimpleUserRepository(ws: WSClient, 
  gs: GremlinServer,
  conf: Configuration) extends UserRepository {


    //TODO: This needs a better approach to be able to support large
    //number of concurrent users.
    var UserTokenMap: mutable.Map[String, String] = mutable.Map()

    //TODO: Handle bulk upload better!
    val BULK_UPLOAD_USER: NiosxUser = 
      new NiosxUser(graphId     = "NA",
        username    = "bulk_upload_user",
        passHash    = SecurityUtils.hashPass(
          conf.get[String]("default.bulk.upload.key")),
        permissions = List("BULK_UPLOAD"),
        role = NiosxAdminRole)
  /**
   * A note on "super_duper_user":
   * In a "perfect" world, recommendation engines should be illegal.
   * People using the platform should be able to recommend stuff to 
   * other people, the platform's task is to create these human connections
   * possible, not to create a trap!
   * A people graph is a powerful tool. It can be used effectively to 
   * supress human connections or to mitigate falsehoods from spreading.
   * The design of the platform decides how people interact. The
   * "super_duper_user" is a step towards building a people graph which
   * enables people driven information sharing. Penalty for bad behaviour
   * would cascade down to all people/actors added to the system by the bad actor
   * and include the actor that introduced this bad actor into the system.
   */
  val SuperDuperUser: String = "superduperuser"
  
  //TODO: Move this to some initialize component/block.
    val suDuUsPass = conf.get[String]("default.admin.password")
    println(suDuUsPass)
    val suDuUser: Option[NiosxUser] = 
      gs
        .Save[NiosxUser]
        .save(
          NiosxUser(graphId = SuperDuperUser,
            username = SuperDuperUser,
            passHash = SecurityUtils.hashPass(suDuUsPass),
            role = NiosxAdminRole,
            permissions = UserRole.getUserPermissions("admin")),
          edge = Darkness) match {
            case f: gs.SaveFailure => None
            case s: gs.SaveSuccess => gs.VertToCC[NiosxUser].convert(s.v)
          }


    def addUser(u: UserInput): String = { 

      val user: NiosxUser = NiosxUser(
        graphId = FileUtils.getNewGraphId,
        username = u.username,
        passHash = SecurityUtils.hashPass(u.password),
        role = UserRole.getRole(u.role),
        permissions = UserRole.getUserPermissions(u.role)) 
       
      gs
        .findUserByUsername(u.username)
        .fold(
          gs
            .Save[NiosxUser]
            .save(user, Darkness) match {//Some(SuperDuperUser))
              case f: gs.SaveFailure => "Could not create user"
              case s: gs.SaveSuccess => s"User ${u.username} created."
            })(_ => s"User ${u.username} already exists!")
        
    }
    
    def editUser(u: UserInput): Option[NiosxUser] = ???
//      val user: NiosxUser = NiosxUser(
//      gremlinServer.Save[NiosxUser].save(user, None)

  /* 
   * Gives back a token wrapped inside an `AuthPayload`
   * that identifies a user session.
   *
   */
  def authenticate(username: String,
    password: String): Option[AuthPayload] = {
      val user: Option[NiosxUser] = username match {
        case "bulk_upload_user" => Some(BULK_UPLOAD_USER)
        case _                  => gs.findUserByUsername(username)
      }

      user.flatMap({ u: NiosxUser => 
        SecurityUtils.checkPass(password, u.passHash) match {
          case false => None
          case true => {
            val token: String = SecurityUtils.genUserToken
            UserTokenMap += (token -> u.username)
            //println("Token saved in map: " + token + ":" + UserTokenMap.get(token))
            Some(AuthPayload(token, u))
          }
        }
      })
  }

  //TODO: Redundant. Remove.
  def findUserByUsername(username: String): Option[NiosxUser] =
    gs.findUserByUsername(username)
      

  /**
   * Gives a user object with permissions.
   */
  def authorise(token: String): Option[NiosxUser] = {
    val user: Option[String] = UserTokenMap.get(token)
    user match {
      case Some("bulk_upload_user") => Some(BULK_UPLOAD_USER)
      case Some(u) => gs.findUserByUsername(u)
      case None => None
    }
  }
 }
