package task

import (
	"code.gitea.io/gitea/models"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/services/reward"
	"code.gitea.io/gitea/services/reward/limiter"
	"fmt"
	"strconv"
	"strings"
)

func Accomplish(action models.Action) {
	defer func() {
		if err := recover(); err != nil {
			combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
			log.Error("PANIC:%v", combinedErr)
		}
	}()
	taskType := models.GetTaskTypeFromAction(action.OpType)
	if taskType == "" {
		log.Info("Accomplish finished.taskType is not exist.action.ID=%d", action.ID)
		return
	}
	actions := make([]models.Action, 0)
	actions = append(actions, action)
	switch taskType {
	//only creating public repo can be rewarded
	case models.TaskCreatePublicRepo:
		if action.Repo.IsPrivate {
			return
		}
	//only creating public image can be rewarded
	case models.TaskCreateImage:
		if action.IsPrivate {
			return
		}
	case models.TaskBindWechat:
		n, err := models.CountWechatBindLog(action.Content, models.WECHAT_BIND)
		if err != nil {
			log.Error("CountWechatBindLog error when accomplish task,err=%v", err)
			return
		}
		//if wechatOpenId has been bound before,the action can not get reward
		if n > 1 && models.IsWechatOpenIdRewarded(action.Content) {

			log.Debug("the wechat account has been bound before,wechatOpenId = %s", action.Content)
			return
		}
	case models.TaskDatasetRecommended:
		datasetIdStr := strings.Split(action.Content, "|")[0]
		datasetId, _ := strconv.ParseInt(datasetIdStr, 10, 64)
		users, err := models.GetAllDatasetContributorByDatasetId(datasetId)
		if err != nil {
			return
		}
		for _, user := range users {
			if user.ID == action.ActUserID {
				continue
			}
			actions = append(actions, models.Action{
				ID:        action.ID,
				OpType:    models.ActionDatasetRecommended,
				ActUserID: action.ActUserID,
				UserID:    user.ID,
				RepoID:    action.RepoID,
				Content:   action.Content,
			})
		}

	}
	batchAccomplish(taskType, actions...)
}

func batchAccomplish(taskType models.TaskType, actions ...models.Action) {
	for _, act := range actions {
		go accomplish(act, taskType)
	}
}

func accomplish(action models.Action, taskType models.TaskType) error {
	defer func() {
		if err := recover(); err != nil {
			combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
			log.Error("PANIC:%v", combinedErr)
		}
	}()
	log.Info("accomplish start.   actionId=%d  userId= %d", action.ID, action.UserID)
	userId := action.UserID
	if !isUserAvailable(userId) {
		return nil
	}

	//get task config
	config, err := GetTaskConfig(string(taskType))
	if err != nil {
		log.Error("GetTaskConfig error,%v", err)
		return err
	}
	if config == nil {
		log.Info("task config not exist,userId=%d taskType=%s", userId, taskType)
		return nil
	}

	//is limited?
	if isLimited(userId, config, models.JustReject) {
		log.Info("task accomplish maximum times are reached,userId=%d taskType=%s", userId, taskType)
		return nil
	}

	//add log
	_, err = models.InsertTaskAccomplishLog(&models.TaskAccomplishLog{
		ConfigId: config.ID,
		TaskCode: config.TaskCode,
		UserId:   userId,
		ActionId: action.ID,
	})
	if err != nil {
		log.Error("InsertTaskAccomplishLog error,%v", err)
		return err
	}

	//reward
	reward.Operate(&models.RewardOperateContext{
		SourceType:       models.SourceTypeAccomplishTask,
		SourceId:         fmt.Sprint(action.ID),
		SourceTemplateId: string(taskType),
		Title:            config.Title,
		Reward: models.Reward{
			Amount: config.AwardAmount,
			Type:   models.GetRewardTypeInstance(config.AwardType),
		},
		TargetUserId: userId,
		RequestId:    fmt.Sprintf("%d_%d", action.ID, userId),
		OperateType:  models.OperateTypeIncrease,
		RejectPolicy: models.FillUp,
	})
	log.Debug("accomplish success,action=%v", action)
	return nil
}

func isLimited(userId int64, config *models.TaskConfig, rejectPolicy models.LimiterRejectPolicy) bool {
	if _, err := limiter.CheckLimit(config.TaskCode, models.LimitTypeTask, userId, 1, rejectPolicy); err != nil {
		log.Error(" isLimited CheckLimit error. %v", err)
		return true
	}
	return false

}

func isUserAvailable(userId int64) bool {
	if userId < 1 {
		return false
	}
	user, err := models.GetUserByID(userId)
	if err != nil || user == nil {
		return false
	}
	if user.IsOrganization() {
		return false
	}
	return true
}
