/*
   Copyright (c) 2016 VMware, Inc. All Rights Reserved.
   Licensed 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 api

import (
	"fmt"
	"net/http"
	"strconv"

	"github.com/vmware/harbor/src/common/api"
	"github.com/vmware/harbor/src/common/dao"
	"github.com/vmware/harbor/src/common/models"
	"github.com/vmware/harbor/src/common/utils/log"
)

// ProjectMemberAPI handles request to /api/projects/{}/members/{}
type ProjectMemberAPI struct {
	api.BaseAPI
	memberID      int
	currentUserID int
	project       *models.Project
}

type memberReq struct {
	Username string `json:"username"`
	UserID   int    `json:"user_id"`
	Roles    []int  `json:"roles"`
}

// Prepare validates the URL and parms
func (pma *ProjectMemberAPI) Prepare() {
	pid, err := strconv.ParseInt(pma.Ctx.Input.Param(":pid"), 10, 64)
	if err != nil {
		log.Errorf("Error parsing project id: %d, error: %v", pid, err)
		pma.CustomAbort(http.StatusBadRequest, "invalid project Id")
		return
	}
	p, err := dao.GetProjectByID(pid)
	if err != nil {
		log.Errorf("Error occurred in GetProjectById, error: %v", err)
		pma.CustomAbort(http.StatusInternalServerError, "Internal error.")
	}

	if p == nil {
		log.Warningf("Project with id: %d does not exist.", pid)
		pma.CustomAbort(http.StatusNotFound, "Project does not exist")
	}
	pma.project = p
	pma.currentUserID = pma.ValidateUser()
	mid := pma.Ctx.Input.Param(":mid")
	if mid == "current" {
		pma.memberID = pma.currentUserID
	} else if len(mid) == 0 {
		pma.memberID = 0
	} else if len(mid) > 0 {
		memberID, err := strconv.Atoi(mid)
		if err != nil {
			log.Errorf("Invalid member Id, error: %v", err)
			pma.CustomAbort(http.StatusBadRequest, "Invalid member id")
		}
		pma.memberID = memberID
	}
}

// Get ...
func (pma *ProjectMemberAPI) Get() {
	pid := pma.project.ProjectID
	if !checkProjectPermission(pma.currentUserID, pid) {
		log.Warningf("Current user, user id: %d does not have permission for project, id: %d", pma.currentUserID, pid)
		pma.RenderError(http.StatusForbidden, "")
		return
	}
	if pma.memberID == 0 { //member id not set return list of the members
		username := pma.GetString("username")
		queryUser := models.User{Username: username}
		userList, err := dao.GetUserByProject(pid, queryUser)
		if err != nil {
			log.Errorf("Failed to query database for member list, error: %v", err)
			pma.RenderError(http.StatusInternalServerError, "Internal Server Error")
			return
		}
		pma.Data["json"] = userList
	} else { //return detail of a  member
		roleList, err := listRoles(pma.memberID, pid)
		if err != nil {
			log.Errorf("Error occurred in GetUserProjectRoles, error: %v", err)
			pma.CustomAbort(http.StatusInternalServerError, "Internal error.")
		}

		if len(roleList) == 0 {
			pma.CustomAbort(http.StatusNotFound, fmt.Sprintf("user %d is not a member of the project", pma.memberID))
		}

		//return empty role list to indicate if a user is not a member
		result := make(map[string]interface{})
		user, err := dao.GetUser(models.User{UserID: pma.memberID})
		if err != nil {
			log.Errorf("Error occurred in GetUser, error: %v", err)
			pma.CustomAbort(http.StatusInternalServerError, "Internal error.")
		}
		result["username"] = user.Username
		result["user_id"] = pma.memberID
		result["roles"] = roleList
		pma.Data["json"] = result
	}
	pma.ServeJSON()
}

// Post ...
func (pma *ProjectMemberAPI) Post() {
	currentUserID := pma.currentUserID
	projectID := pma.project.ProjectID
	if !hasProjectAdminRole(currentUserID, projectID) {
		log.Warningf("Current user, id: %d does not have project admin role for project, id:", currentUserID, projectID)
		pma.RenderError(http.StatusForbidden, "")
		return
	}

	var req memberReq
	pma.DecodeJSONReq(&req)
	username := req.Username
	userID := checkUserExists(username)
	if userID <= 0 {
		log.Warningf("User does not exist, user name: %s", username)
		pma.RenderError(http.StatusNotFound, "User does not exist")
		return
	}
	rolelist, err := dao.GetUserProjectRoles(userID, projectID)
	if err != nil {
		log.Errorf("Error occurred in GetUserProjectRoles, error: %v", err)
		pma.CustomAbort(http.StatusInternalServerError, "Internal error.")
	}
	if len(rolelist) > 0 {
		log.Warningf("user is already added to project, user id: %d, project id: %d", userID, projectID)
		pma.RenderError(http.StatusConflict, "user is ready in project")
		return
	}

	if len(req.Roles) <= 0 || len(req.Roles) > 1 {
		pma.CustomAbort(http.StatusBadRequest, "only one role is supported")
	}

	rid := req.Roles[0]
	if !(rid == models.PROJECTADMIN ||
		rid == models.DEVELOPER ||
		rid == models.GUEST) {
		pma.CustomAbort(http.StatusBadRequest, "invalid role")
	}

	err = dao.AddProjectMember(projectID, userID, rid)
	if err != nil {
		log.Errorf("Failed to update DB to add project user role, project id: %d, user id: %d, role id: %d", projectID, userID, rid)
		pma.RenderError(http.StatusInternalServerError, "Failed to update data in database")
		return
	}
}

// Put ...
func (pma *ProjectMemberAPI) Put() {
	currentUserID := pma.currentUserID
	pid := pma.project.ProjectID
	if !hasProjectAdminRole(currentUserID, pid) {
		log.Warningf("Current user, id: %d does not have project admin role for project, id:", currentUserID, pid)
		pma.RenderError(http.StatusForbidden, "")
		return
	}

	mid := pma.memberID

	var req memberReq
	pma.DecodeJSONReq(&req)
	roleList, err := dao.GetUserProjectRoles(mid, pid)
	if len(roleList) == 0 {
		log.Warningf("User is not in project, user id: %d, project id: %d", mid, pid)
		pma.RenderError(http.StatusNotFound, "user not exist in project")
		return
	}
	//TODO: delete and insert should in one transaction
	//delete user project role record for the given user
	err = dao.DeleteProjectMember(pid, mid)
	if err != nil {
		log.Errorf("Failed to delete project roles for user, user id: %d, project id: %d, error: %v", mid, pid, err)
		pma.RenderError(http.StatusInternalServerError, "Failed to update data in DB")
		return
	}
	//insert roles in request
	for _, rid := range req.Roles {
		err = dao.AddProjectMember(pid, mid, int(rid))
		if err != nil {
			log.Errorf("Failed to update DB to add project user role, project id: %d, user id: %d, role id: %d", pid, mid, rid)
			pma.RenderError(http.StatusInternalServerError, "Failed to update data in database")
			return
		}
	}
}

// Delete ...
func (pma *ProjectMemberAPI) Delete() {
	currentUserID := pma.currentUserID
	pid := pma.project.ProjectID
	if !hasProjectAdminRole(currentUserID, pid) {
		log.Warningf("Current user, id: %d does not have project admin role for project, id:", currentUserID, pid)
		pma.RenderError(http.StatusForbidden, "")
		return
	}

	mid := pma.memberID

	err := dao.DeleteProjectMember(pid, mid)
	if err != nil {
		log.Errorf("Failed to delete project roles for user, user id: %d, project id: %d, error: %v", mid, pid, err)
		pma.RenderError(http.StatusInternalServerError, "Failed to update data in DB")
		return
	}
}
