package org

import (
	"encoding/json"
	"fmt"
	"net/http"

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

	"github.com/pingcap-inc/clinic/pkg/api/common"
	error2 "github.com/pingcap-inc/clinic/pkg/api/error"
	"github.com/pingcap-inc/clinic/pkg/auth/session"
	common2 "github.com/pingcap-inc/clinic/pkg/common"
	"github.com/pingcap-inc/clinic/pkg/config"
	_const "github.com/pingcap-inc/clinic/pkg/const"
	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/db/model"
	"github.com/pingcap-inc/clinic/pkg/rbac"
	"github.com/pingcap-inc/clinic/pkg/stream"
	"github.com/pingcap-inc/clinic/pkg/task_manager"
)

// @Summary		Get the organization task list
// @Tags			Organization
// @Description	get organization task list
// @Accept			json
// @Param			x-csrf-token	header	string	true	"get value from login.ValidationResp response"
// @Param			oid				path	string	true	"organization id"
// @Success		200				{array}	TaskStatus
// @Router			/orgs/{oid}/tasks [get]
func (o *OrganizationService) GetTaskStatus(c *gin.Context) {
	u := session.GetUser(c)
	if u == nil {
		_ = c.Error(error2.ErrNotFound.New("the user is not found"))
		return
	}

	oid, err := common.GetOrgIDFromAPIPath(c)
	if err != nil {
		_ = c.Error(error2.ErrNotFound.New("unknown organization id"))
		return
	}

	ok, err := o.rbac.Auth(u.UserID, u.Internal, rbac.Cluster(), rbac.ReadActing(), oid)
	if err != nil || !ok {
		_ = c.Error(error2.ErrNotFound.New("the user is not found"))
		return
	}

	ss, err := getTaskStatus(u.UserID, oid, o.cfg, *o.taskManagerCli)
	if err != nil {
		_ = c.Error(error2.ErrInternal.New("get task list failed"))
		return
	}
	c.JSON(http.StatusOK, ss)
}

type SortedUploadFile struct {
	file    *db.UploadFile
	rebuild bool
}

func SortUploadFiles(sortedByUpdateTime, sortedByCreateTime []db.UploadFile, limit int) []*SortedUploadFile {
	m := len(sortedByUpdateTime)
	n := len(sortedByCreateTime)
	sorted := make([]*SortedUploadFile, 0, m+n)

	p1, p2 := 0, 0
	for {
		if p1 == m {
			leftCreateTime := sortedByCreateTime[p2:]
			for i := 0; i < len(leftCreateTime); i++ {
				sorted = append(sorted, &SortedUploadFile{
					file: &leftCreateTime[i],
				})
			}
			break
		}

		if p2 == n {
			leftUploadTime := sortedByUpdateTime[p1:]
			for i := 0; i < len(leftUploadTime); i++ {
				sorted = append(sorted, &SortedUploadFile{
					file:    &leftUploadTime[i],
					rebuild: true,
				})
			}
			break
		}

		if sortedByUpdateTime[p1].UpdatedAt.After(sortedByCreateTime[p2].CreatedAt) {
			sorted = append(sorted, &SortedUploadFile{
				file:    &sortedByUpdateTime[p1],
				rebuild: true,
			})
			p1++
		} else {
			sorted = append(sorted, &SortedUploadFile{
				file: &sortedByCreateTime[p2],
			})
			p2++
		}
	}

	size := limit
	if limit > len(sorted) {
		size = len(sorted)
	}
	return sorted[:size]
}

func getTaskStatus(userID, orgID uint, _ *config.Config, cli task_manager.TaskManagerClient) ([]*TaskStatus, error) {
	createdByFiles, err := model.ListUploadFilesInOrg(userID, orgID, 5, model.SortedByCreatedAt, false, false, false)
	if err != nil {
		log.Error("list files failed", zap.Uint("user", userID), zap.Uint("org", orgID))
		return nil, err
	}

	updatedByFiles, err := model.ListUploadFilesInOrg(userID, orgID, 5, model.SortedByUpdatedAt, true, true, true)
	if err != nil {
		log.Error("list files failed", zap.Uint("user", userID), zap.Uint("org", orgID))
		return nil, err
	}

	files := SortUploadFiles(updatedByFiles, createdByFiles, 5)

	ss := make([]*TaskStatus, 0)
	for _, f := range files {
		if f.rebuild {
			ss = append(ss, ConvertRebuildType(cli, f.file)...)
		} else {
			ss = append(ss, ConvertUploadType(f.file))
		}
	}

	return ss, nil
}

func ConvertUploadType(file *db.UploadFile) *TaskStatus {
	return &TaskStatus{
		TaskType: "Upload",
		TaskName: file.FileOriginalName,
		Process:  getUploadProcess(file),
		Size:     file.FileLen,
	}
}

func ConvertRebuildType(cli task_manager.TaskManagerClient, file *db.UploadFile) []*TaskStatus {
	ss := make([]*TaskStatus, 0)
	for _, s := range getRebuildProcess(cli, file) {
		ss = append(ss, &TaskStatus{
			TaskType: "Rebuild",
			TaskName: fmt.Sprintf("%s-%s: %s-%s", file.ClusterName, s.Key, common.CstTimeZone(file.BeginTime), common.CstTimeZone(file.EndTime)),
			Process:  s.Value,
		})
	}
	return ss
}

func getRebuildProcess(cli task_manager.TaskManagerClient, file *db.UploadFile) []*common.PairInt {
	data, err := cli.Status(file.FileUUID)
	if err != nil {
		log.Error("build request failed", zap.String("file", file.FileUUID), zap.Error(err))
		return nil
	}

	var val common.TaskStatusResp
	if err := json.Unmarshal(data, &val); err != nil {
		log.Error("request importer response failed", zap.String("file", file.FileUUID), zap.Error(err))
		return nil
	}

	pairs := make([]*common.PairInt, 0)

	stream.OfSlice(val.Items).Filter(func(item common.TaskStatus) bool {
		return item.Status > 0
	}).Map(func(item common.TaskStatus) *common.PairInt {
		ttype := ""
		if _const.TaskType(item.TaskType) == _const.LogTask {
			ttype = "Log"
		}

		if _const.TaskType(item.TaskType) == _const.MetricTask {
			ttype = "Metric"
		}

		if ttype == "" {
			return nil
		}

		return &common.PairInt{
			Key:   ttype,
			Value: item.Status,
		}
	}).Filter(func(p *common.PairInt) bool {
		return p != nil
	}).CollectEmptySlice(&pairs)

	return pairs
}

func getUploadProcess(file *db.UploadFile) int {
	if file.UploadStatus == string(db.StatusSuccess) {
		return 100
	}

	if file.UploadStatus == string(db.StatusFailed) {
		return -2
	}

	parts, err := model.QueryUploadFileParts(file.FileUUID)
	if err != nil {
		log.Error("query upload file parts failed", zap.String("file", file.FileUUID), zap.Error(err))
		return -1
	}

	totalBlocks := computeTotalBlock(file.FileLen, common2.GetBlockSizeInBytes(file.FileLen))

	if len(parts) >= totalBlocks {
		return 100
	}

	v := float64(len(parts)) / float64(totalBlocks)
	return int(v * 100)
}

func computeTotalBlock(fileSize, blockSize int64) int {
	if fileSize%blockSize == 0 {
		return int(fileSize / blockSize)
	}

	return int(fileSize/blockSize) + 1
}
