/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.texera.web.resource.dashboard.user.project

import io.dropwizard.auth.Auth
import org.apache.texera.auth.SessionUser
import org.apache.texera.dao.SqlServer
import org.apache.texera.dao.jooq.generated.Tables.{
  DATASET_USER_ACCESS,
  PROJECT_USER_ACCESS,
  USER,
  WORKFLOW_USER_ACCESS
}
import org.apache.texera.dao.jooq.generated.enums.PrivilegeEnum
import org.apache.texera.dao.jooq.generated.tables.daos.{ProjectDao, ProjectUserAccessDao, UserDao}
import org.apache.texera.dao.jooq.generated.tables.pojos.ProjectUserAccess
import org.apache.texera.web.model.common.AccessEntry
import org.apache.texera.web.resource.dashboard.user.project.ProjectAccessResource.userHasWriteAccess
import org.jooq.DSLContext

import java.util
import javax.annotation.security.RolesAllowed
import javax.ws.rs._
import javax.ws.rs.core.MediaType

object ProjectAccessResource {
  private def context: DSLContext =
    SqlServer
      .getInstance()
      .createDSLContext()

  def userHasWriteAccess(pid: Integer, uid: Integer): Boolean = {
    getProjectAccessPrivilege(pid, uid) == PrivilegeEnum.WRITE
  }

  def getProjectAccessPrivilege(pid: Integer, uid: Integer): PrivilegeEnum = {
    Option(
      context
        .select(PROJECT_USER_ACCESS.PRIVILEGE)
        .from(WORKFLOW_USER_ACCESS)
        .where(
          PROJECT_USER_ACCESS.PID
            .eq(pid)
            .and(DATASET_USER_ACCESS.UID.eq(uid))
        )
        .fetchOneInto(classOf[PrivilegeEnum])
    ).getOrElse(PrivilegeEnum.NONE)
  }
}

@Produces(Array(MediaType.APPLICATION_JSON))
@RolesAllowed(Array("REGULAR", "ADMIN"))
@Path("/access/project")
class ProjectAccessResource() {
  private def context: DSLContext =
    SqlServer
      .getInstance()
      .createDSLContext()
  private def userDao = new UserDao(context.configuration())
  private def projectDao = new ProjectDao(context.configuration)
  private def projectUserAccessDao = new ProjectUserAccessDao(context.configuration)

  /**
    * This method returns the owner of a project
    *
    * @param pid ,  project id
    * @return ownerEmail,  the owner's email
    */
  @GET
  @Path("/owner/{pid}")
  def getOwner(@PathParam("pid") pid: Integer): String = {
    userDao.fetchOneByUid(projectDao.fetchOneByPid(pid).getOwnerId).getEmail
  }

  /**
    * Returns information about all current shared access of the given project
    *
    * @param pid project id
    * @return a List of email/permission pair
    */
  @GET
  @Path("/list/{pid}")
  def getAccessList(
      @PathParam("pid") pid: Integer
  ): util.List[AccessEntry] = {
    context
      .select(
        USER.EMAIL,
        USER.NAME,
        PROJECT_USER_ACCESS.PRIVILEGE
      )
      .from(PROJECT_USER_ACCESS)
      .join(USER)
      .on(USER.UID.eq(PROJECT_USER_ACCESS.UID))
      .where(
        PROJECT_USER_ACCESS.PID
          .eq(pid)
          .and(PROJECT_USER_ACCESS.UID.notEqual(projectDao.fetchOneByPid(pid).getOwnerId))
      )
      .fetchInto(classOf[AccessEntry])
  }

  /**
    * This method shares a project to a user with a specific access type
    *
    * @param pid       the given project
    * @param email     the email which the access is given to
    * @param privilege the type of Access given to the target user
    * @return rejection if user not permitted to share the project or Success Message
    */
  @PUT
  @Path("/grant/{pid}/{email}/{privilege}")
  def grantAccess(
      @PathParam("pid") pid: Integer,
      @PathParam("email") email: String,
      @PathParam("privilege") privilege: String,
      @Auth user: SessionUser
  ): Unit = {
    if (!userHasWriteAccess(pid, user.getUid)) {
      throw new ForbiddenException(s"You do not have permission to modify project $pid")
    }

    projectUserAccessDao.merge(
      new ProjectUserAccess(
        userDao.fetchOneByEmail(email).getUid,
        pid,
        PrivilegeEnum.valueOf(privilege)
      )
    )
  }

  /**
    * Revoke a user's access to a file
    *
    * @param pid   the id of the file
    * @param email the email of target user whose access is about to be revoked
    * @return A successful resp if granted, failed resp otherwise
    */
  @DELETE
  @Path("/revoke/{pid}/{email}")
  def revokeAccess(
      @PathParam("pid") pid: Integer,
      @PathParam("email") email: String,
      @Auth user: SessionUser
  ): Unit = {
    if (!userHasWriteAccess(pid, user.getUid)) {
      throw new ForbiddenException(s"You do not have permission to modify project $pid")
    }

    context
      .delete(PROJECT_USER_ACCESS)
      .where(
        PROJECT_USER_ACCESS.UID
          .eq(userDao.fetchOneByEmail(email).getUid)
          .and(PROJECT_USER_ACCESS.PID.eq(pid))
      )
      .execute()
  }
}
