package oldreport

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

	"github.com/gin-gonic/gin"
	"github.com/pingcap/log"
	"go.uber.org/zap"
	"gorm.io/gorm"

	"github.com/pingcap-inc/clinic/pkg/api/common"
	error2 "github.com/pingcap-inc/clinic/pkg/api/error"
	"github.com/pingcap-inc/clinic/pkg/compute_engine"
	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/db/model"
	"github.com/pingcap-inc/clinic/pkg/resource_limit"
)

// @Summary		Touch New Comparison Report Job
// @Tags			Report
// @Description	touch report in on cluster
// @Accept			json
// @Param			x-csrf-token	header		string						true	"get value from login.ValidationResp response"
// @Param			oid				path		string						true	"organization id"
// @Param			cid				path		string						true	"cluster id"
// @Param			report-param	body		TouchComparisonReportReq	true	"comparison report param"
// @Success		200				{object}	ReportResp
// @Router			/compute-engine/{oid}/{cid}/comparison [post]
func (rs *ReportService) TouchComparisonReport(c *gin.Context) {
	user, org, _, ok, err := common.CanReadCluster(c, rs.rbac, c.Param("cid"))
	if err != nil {
		_ = c.Error(error2.ErrNotFound.New("%s", err.Error()))
		return
	}
	if !ok {
		_ = c.Error(error2.ErrForbidden.New("permission deny"))
		return
	}

	ComparisonReportReq := &TouchComparisonReportReq{}
	err = c.BindJSON(ComparisonReportReq)
	if err != nil {
		log.Error("bind json failed", zap.Error(err))
		_ = c.Error(error2.ErrBadRequest.New("bad parameter"))
		return
	}

	val := &common.Comparison{
		Creator:   user.Email,
		ClusterID: c.Param("cid"),
		OrgID:     org.ID,
		OrgType:   org.OrgType,
		Args: db.ComparisonArg{
			PackageID1: ComparisonReportReq.PackageID1,
			StartTime1: ComparisonReportReq.StartTime1,
			EndTime1:   ComparisonReportReq.EndTime1,
			PackageID2: ComparisonReportReq.PackageID2,
			StartTime2: ComparisonReportReq.StartTime2,
			EndTime2:   ComparisonReportReq.EndTime2,
		},
	}
	_, err = prehandleComparisonParam(val)
	if err != nil {
		log.Error("prehandle param failed", zap.Error(err))
		_ = c.Error(error2.ErrBadRequest.New("%s", err.Error()))
		return
	}
	h, err := common.Hash(val.Args)
	if err != nil {
		log.Error("hash value failed", zap.Error(err))
		_ = c.Error(error2.ErrBadRequest.New("bad parameter"))
		return
	}
	if !user.Internal && !rs.quotalimiter.AskForResource(resource_limit.CmpReportCntPerCluster, 1, org.ID, c.Param("cid"), h) {
		_ = c.Error(error2.ErrPreConditionFailed.New("%s", resource_limit.CmpReportCntPerCluster.Reason()))
		return
	}
	resp, err := rs.taskManager.TouchComparisonReport(*val)
	if err != nil {
		log.Error("touch compute engine failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("touch comparison failed"))
		return
	}
	c.JSON(http.StatusOK, ReportResp{
		TaskID: fmt.Sprintf("%d", resp.ID),
	})
}

// @Summary		list comparison report
// @Tags			Report
// @Description	list comparison report
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Param			cid				path		string	true	"cluster id"
// @Param			limit			query		int		false	"the count per page"
// @Param			page			query		int		false	"the page index"
// @Param			brief			query		bool	false	"if need data"
// @Success		200				{object}	ComparisonReportResp
// @Router			/compute-engine/{oid}/{cid}/comparison [get]
func (rs *ReportService) ListComparisonReport(c *gin.Context) {
	_, org, _, ok, err := common.CanReadCluster(c, rs.rbac, c.Param("cid"))
	if err != nil {
		_ = c.Error(error2.ErrNotFound.New("%s", err.Error()))
		return
	}
	if !ok {
		_ = c.Error(error2.ErrForbidden.New("permission deny"))
		return
	}

	var (
		page  int
		limit int
	)
	pageParam := c.Query("page")
	if pageParam == "" {
		pageParam = "0"
	}
	page, err = strconv.Atoi(pageParam)
	if err != nil {
		log.Error("parse param failed", zap.Error(err))
		_ = c.Error(error2.ErrBadRequest.New("parse aren't int"))
	}

	limitParam := c.Query("limit")
	if limitParam == "" {
		limitParam = "0"
	}
	limit, err = strconv.Atoi(limitParam)
	if err != nil {
		log.Error("parse param failed", zap.Error(err))
		_ = c.Error(error2.ErrBadRequest.New("parse aren't int"))
	}

	reports, total, err := model.QueryComparisonReport(org.ID, c.Param("cid"), page, limit)
	if err != nil {
		log.Error("query db failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("query db failed"))
	}
	comparisonReportResp := &ComparisonReportResp{
		Total:   uint(total),
		Reports: make([]ComparisonReport, 0),
	}
	for _, report := range reports {
		comparisonReportResp.Reports = append(comparisonReportResp.Reports, ComparisonReport{
			Status:    report.Status,
			Creator:   report.Creator,
			ID:        report.ID,
			Args:      *report.ComparisonArg,
			CreatedAt: uint(report.CreatedAt.Unix()),
		})
	}
	c.JSON(http.StatusOK, comparisonReportResp)
}

// @Summary		get comparison report
// @Tags			Report
// @Description	list comparison report
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Param			cid				path		string	true	"cluster id"
// @Param			rid				path		string	true	"report id"
// @Param			brief			query		bool	false	"if need data"
// @Success		200				{object}	compute_engine.ComparisonReport
// @Router			/compute-engine/{oid}/{cid}/comparison/{rid} [get]
func (rs *ReportService) GetComparisonReport(c *gin.Context) {
	_, _, _, ok, err := common.CanReadCluster(c, rs.rbac, c.Param("cid"))
	if err != nil {
		_ = c.Error(error2.ErrNotFound.New("%s", err.Error()))
		return
	}
	if !ok {
		_ = c.Error(error2.ErrForbidden.New("permission deny"))
		return
	}

	id, err := strconv.ParseUint(c.Param("rid"), 10, 64)
	if err != nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid rid"))
		return
	}

	brief := false
	briefstr := c.Query("brief")
	if briefstr != "" {
		brief, err = strconv.ParseBool(briefstr)
		if err != nil {
			log.Error("parse brief failed", zap.Error(err))
			_ = c.Error(error2.ErrBadRequest.New("brief need be bool"))
			return
		}
	}

	report, err := model.GetComparisonReportByID(uint(id))
	if err == gorm.ErrRecordNotFound {
		_ = c.Error(error2.ErrNotFound.New("need to compute comparison report"))
		return
	}
	if err != nil {
		log.Error("GetComparisonReportByID failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("db connection failed"))
		return
	}

	var rep any
	if !brief {
		rep, err = compute_engine.GlobalCMClient.FetchComparisonReport(report.ReportUUID)
		if err != nil {
			log.Error("compute engine client failed", zap.Error(err))
			_ = c.Error(error2.ErrInternal.New("failed request to remote service"))
			return
		}
	} else {
		rep = &compute_engine.ComparisonReport{
			Status:    report.Status,
			CreatedAt: int(report.CreatedAt.Unix()),
			UpdatedAt: int(report.CreatedAt.Unix()),
		}
	}
	c.JSON(http.StatusOK, rep)
}

// @Summary		delete comparison report
// @Tags			Report
// @Description	delete comparison report
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Param			cid				path		string	true	"cluster id"
// @Param			rid				path		string	true	"report id"
// @Success		200				{string}	success
// @Router			/compute-engine/{oid}/{cid}/comparison/{rid} [delete]
func (rs *ReportService) DeleteComparisonReport(c *gin.Context) {
	_, _, _, ok, err := common.CanReadCluster(c, rs.rbac, c.Param("cid"))
	if err != nil {
		_ = c.Error(error2.ErrNotFound.New("%s", err.Error()))
		return
	}
	if !ok {
		_ = c.Error(error2.ErrForbidden.New("permission deny"))
		return
	}

	id, err := strconv.ParseUint(c.Param("rid"), 10, 64)
	if err != nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid rid"))
		return
	}

	err = model.HideComparisonReportByID(uint(id))
	if err == gorm.ErrRecordNotFound {
		_ = c.Error(error2.ErrNotFound.New("report not exist"))
		return
	}
	if err != nil {
		log.Error("HideHealthReportByID failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("db connection failed"))
		return
	}

	c.JSON(http.StatusOK, common.SuccessResponse())
}

func prehandleComparisonParam(val *common.Comparison) (bool, error) {
	switch val.OrgType {
	case "cloud":
		if !checkTime(val.Args.StartTime1, val.Args.EndTime1) || !checkTime(val.Args.StartTime2, val.Args.EndTime2) {
			return false, fmt.Errorf("time range should in [%d, %d] minutes", common.ComparisonReportMinDataRange/60, common.ComparisonReportMaxDataRange/60)
		}
		return true, nil
	default:
		if len(val.Args.PackageID1) != 0 {
			for _, uf := range val.Args.PackageID1 {
				_, err := model.QueryUploadFile(uf)
				if err != nil {
					log.Error("query upload file failed", zap.Error(err))
					return false, err
				}
				if !checkTime(val.Args.StartTime1, val.Args.EndTime1) {
					val.Args.StartTime1 = val.Args.EndTime1 - common.ComparisonReportMaxDataRange
				}
			}
			return true, nil
		} else if checkTime(val.Args.StartTime1, val.Args.EndTime1) {
			ufs, _, err := model.ListUploadFiles(val.OrgID, val.ClusterID, 0, 0, int64(val.Args.StartTime1), int64(val.Args.EndTime1))
			if err != nil {
				log.Error("query db failed", zap.Error(err))
				return false, err
			}
			if len(ufs) == 0 {
				return false, fmt.Errorf("%s", common.ErrEmptyData)
			}
			for _, uf := range ufs {
				val.Args.PackageID1 = append(val.Args.PackageID1, uf.FileUUID)
			}
		} else {
			return false, fmt.Errorf("too large or small time range")
		}
		if len(val.Args.PackageID2) != 0 {
			for _, uf := range val.Args.PackageID2 {
				_, err := model.QueryUploadFile(uf)
				if err != nil {
					log.Error("query upload file failed", zap.Error(err))
					return false, err
				}
			}
			if !checkTime(val.Args.StartTime2, val.Args.EndTime2) {
				val.Args.StartTime1 = val.Args.EndTime1 - common.ComparisonReportMaxDataRange
			}
			return true, nil
		} else if checkTime(val.Args.StartTime2, val.Args.EndTime2) {
			ufs, _, err := model.ListUploadFiles(val.OrgID, val.ClusterID, 0, 0, int64(val.Args.StartTime2), int64(val.Args.EndTime2))
			if err != nil {
				log.Error("query db failed", zap.Error(err))
				return false, err
			}
			for _, uf := range ufs {
				val.Args.PackageID2 = append(val.Args.PackageID2, uf.FileUUID)
			}
			if len(ufs) == 0 {
				return false, fmt.Errorf("%s", common.ErrEmptyData)
			}
		} else {
			return false, fmt.Errorf("%s", common.ErrTimeRange)
		}
	}
	return true, nil
}

func checkTime(startTime, endTime uint) bool {
	if startTime != 0 && endTime != 0 &&
		endTime-startTime >= common.ComparisonReportMinDataRange &&
		endTime-startTime <= common.ComparisonReportMaxDataRange {
		return true
	}
	return false
}
