package budget

import (
	"strconv"

	"github.com/gin-gonic/gin"

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

	"github.com/pingcap-inc/pchub/internal/pkg/code"
	"github.com/pingcap-inc/pchub/pkg/errors"
	"github.com/pingcap-inc/pchub/pkg/util/ginres"

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

type BudgetController struct {
	srv srvv1.Service
}

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

func (b *BudgetController) ListLatestFY(c *gin.Context) {
	log.L(c).Info("BudgetController.List controller function called")

	params := ginres.ParseQuery(c, "mid")
	if params["mid"] == "" {
		log.L(c).Error("query param 'mid' is empty")
		ginres.WriteResponse(c, errors.WithCode(code.ErrValidation, "query param 'mid' is empty"), nil)

		return
	}
	years := 1
	if params["fy"] != "" {
		if num, err := strconv.Atoi(params["fy"]); err == nil {
			years = num
		}
	}

	res, err := b.srv.Budget().ListLatestFY(c, params["mid"], years)
	if err != nil {
		ginres.WriteResponse(c, err, nil)

		return
	}

	ginres.WriteResponse(c, nil, res)
}

//nolint:gocognit,gocyclo,cyclop // acceptable
func (b *BudgetController) SetBudget(c *gin.Context) {
	log.L(c).Info("BudgetController.SetBudget controller function called.")

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

		return
	}

	if bd.ManagementID == "" {
		ginres.WriteResponse(c, errors.WithCode(code.ErrValidation, "'managementID' missing in request body"), nil)

		return
	}

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

		return
	}

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

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

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

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

		return
	}

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

		return
	}

	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 set budget directly"), nil)

		return
	}

	for _, bit := range bd.Items {
		if bd.BudgetType == modelv1.BudgetApprTypeNew {
			n := &modelv1.CloudResourceBudget{
				ResourceID: resource.ID,
				Amount:     bit.After,
				Month:      bit.Month,
			}

			_, err = b.srv.Budget().Create(c, n)
			if err != nil {
				log.L(c).Error("failed to create budget, err: " + err.Error())

				return
			}
		} else {
			err = b.srv.Budget().UpdateBudget(c, resource.ID, bit.Month, bit.After)
			if err != nil {
				log.L(c).Error("failed to update budget, err: " + err.Error())

				return
			}
		}
	}

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