package resource

import (
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/pingcap-inc/pchub/internal/pkg/middleware"

	modelv1 "github.com/pingcap-inc/pchub/model/v1"

	srvv1 "github.com/pingcap-inc/pchub/internal/apiserver/service/v1"
	"github.com/pingcap-inc/pchub/internal/apiserver/store"
	"github.com/pingcap-inc/pchub/internal/pkg/code"
	"github.com/pingcap-inc/pchub/pkg/errors"
	"github.com/pingcap-inc/pchub/pkg/log"
	"github.com/pingcap-inc/pchub/pkg/util/ginres"
)

type ResourceController struct {
	srv srvv1.Service
}

func NewResourceController(store store.Factory) *ResourceController {
	return &ResourceController{
		srv: srvv1.NewService(store),
	}
}

func (r *ResourceController) UpdateInfo(c *gin.Context) {
	log.L(c).Info("ResourceController.UpdateInfo controller function called.")

	var b modelv1.UpdateCloudResourceBody
	err := c.ShouldBindJSON(&b)
	if err != nil {
		ginres.WriteResponse(c, errors.WithCode(code.ErrInvalidJSON, ""), nil)

		return
	}

	if b.MID == "" {
		ginres.WriteResponse(c, errors.WithCode(code.ErrValidation, "'mid' missing in request body"), nil)

		return
	}

	resource, err := r.srv.CloudResource().GetByManagementID(c, b.MID)
	if err != nil {
		ginres.WriteResponse(c, err, nil)

		return
	}

	authingId, err := middleware.GetAuthingId(c)
	if err != nil {
		ginres.WriteResponse(c, err, nil)

		return
	}
	user, err := r.srv.User().GetUserByID(c, authingId)
	if err != nil {
		ginres.WriteResponse(c, err, nil)

		return
	}

	if resource.Owner.LarkUserID != user.LarkUserID {
		log.L(c).Error("only owner can update info, " + err.Error())
		ginres.WriteResponse(c, errors.WithCode(code.ErrPermissionDenied, "only owner can update info"), nil)

		return
	}

	resource.Desc = b.Description
	err = r.srv.CloudResource().UpdateInfo(c, resource)
	if err != nil {
		ginres.WriteResponse(c, errors.WithCode(code.ErrUnknown, err.Error()), nil)

		return
	}

	ginres.WriteResponse(c, nil, "ok")
}

func (r *ResourceController) Detail(c *gin.Context) {
	log.L(c).Info("ResourceController.Deatil controller function called.")

	params := ginres.ParseQuery(c, "mid")
	if params["mid"] == "" {
		log.L(c).Error("No valid managementID specified.")
		ginres.WriteResponse(c, errors.WithCode(code.ErrValidation, "No valid managementID specified."), nil)

		return
	}

	res, err := r.srv.CloudResource().Detail(c, params["mid"])
	if err != nil {
		ginres.WriteResponse(c, err, nil)

		return
	}

	ginres.WriteResponse(c, nil, res)
}

func (r *ResourceController) NameCheck(c *gin.Context) {
	log.L(c).Info("ResourceController.NameCheck controller function called.")

	params := ginres.ParseQuery(c, "providerID", "name")
	if params["providerID"] == "" || params["name"] == "" {
		log.L(c).Error("No valid provider code or name specified.")
		ginres.WriteResponse(c, errors.WithCode(code.ErrValidation, "No valid provider code or name specified."), nil)

		return
	}

	res, err := r.srv.CloudResource().NameCheck(c, params["providerID"], params["name"])
	if err != nil {
		ginres.WriteResponse(c, err, nil)

		return
	}

	ginres.WriteResponse(c, nil, res)
}

func (r *ResourceController) Search(c *gin.Context) {
	log.L(c).Info("ResourceController.Search controller function called.")

	params := ginres.ParseQuery(c, "s")

	trimStr := strings.TrimSpace(params["s"])
	if trimStr == "" {
		log.L(c).Error("No valid searchInfo specified.")
		ginres.WriteResponse(c, errors.WithCode(code.ErrValidation, "No valid searchInfo specified."), nil)

		return
	}

	res, err := r.srv.CloudResource().Search(c, strings.ToLower(trimStr))
	if err != nil {
		ginres.WriteResponse(c, err, nil)

		return
	}

	ginres.WriteResponse(c, nil, res)
}

//nolint:gocognit,gocyclo,cyclop // accepectable
func (r *ResourceController) Transfer(c *gin.Context) {
	log.L(c).Info("ResourceController.Transfer controller function called.")

	var b modelv1.TransferResourceBody
	err := c.ShouldBindJSON(&b)
	if err != nil {
		ginres.WriteResponse(c, errors.WithCode(code.ErrInvalidJSON, ""), nil)

		return
	}

	if b.MID == "" {
		ginres.WriteResponse(c, errors.WithCode(code.ErrValidation, "'mid' missing in request body"), nil)

		return
	}

	resource, err := r.srv.CloudResource().GetByManagementID(c, b.MID)
	if err != nil {
		ginres.WriteResponse(c, err, nil)

		return
	}
	depts, err := r.srv.Department().GetChainByLarkID(c, resource.DepartLarkID)
	if err != nil {
		ginres.WriteResponse(c, err, nil)

		return
	}
	authingId, err := middleware.GetAuthingId(c)
	if err != nil {
		ginres.WriteResponse(c, err, nil)

		return
	}
	user, err := r.srv.User().GetUserByID(c, authingId)
	if err != nil {
		ginres.WriteResponse(c, err, nil)

		return
	}
	perms, err := r.srv.PCHPermission().ListByLarkUID(c, user.LarkUserID)
	if err != nil {
		ginres.WriteResponse(c, err, nil)

		return
	}

	dptMap := make(map[string]bool)
	for _, dpt := range *depts {
		dptMap[dpt.LarkID] = true
	}

	hasPermission := false
	for _, perm := range perms {
		if perm.Type == modelv1.PCHPermissionTypeAdmin {
			hasPermission = true

			break
		}
		if perm.Type == modelv1.PCHPermissionTypeDepartment {
			if _, ok := dptMap[perm.Domain]; ok {
				hasPermission = true

				break
			}
		}
		if perm.Type == modelv1.PCHPermissionTypeResource {
			if perm.Domain == resource.ManagementID {
				hasPermission = true

				break
			}
		}
	}

	if !hasPermission {
		log.L(c).Error("you have no permission to transfer resource")
		ginres.WriteResponse(c, errors.WithCode(code.ErrPermissionDenied, "no permission to transfer resource"), nil)

		return
	}

	if b.Description != "" {
		resource.Desc = b.Description
	}
	if b.DepartmentID != "" {
		resource.DepartLarkID = b.DepartmentID
	}
	if b.OwnerID != "" {
		resource.OwnerLarkID = b.OwnerID
	}

	err = r.srv.CloudResource().UpdateInfo(c, resource)
	if err != nil {
		ginres.WriteResponse(c, errors.WithCode(code.ErrUnknown, err.Error()), nil)

		return
	}

	ginres.WriteResponse(c, nil, "ok")
}
