package globalapi

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/mitchellh/mapstructure"
	"github.com/pingcap/log"
	"github.com/tidbcloud/observability/pkg/common/message"
	"github.com/tidbcloud/observability/pkg/common/utils"
	"github.com/tidbcloud/observability/pkg/globalservice"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"github.com/tidbcloud/tiinf/transport/http/tigin"
	"go.uber.org/zap"
)

type auditHelper struct {
	event auditlog.EventType
	fn    func(result message.Result, details auditlog.Details)
}

var (
	// should disable it for test only
	EnableAuditLog        bool
	handlerToAuditHelpers = map[string]*auditHelper{
		"UpdateAlertRules": &auditHelper{
			event: auditlog.EventUpdateAlertRule,
			fn:    EnrichUpdateAlertRulesDetail,
		},
	}
)

func AuditIfNeeded(c *gin.Context, resp ResponseResult, keyErr error) {
	if !EnableAuditLog {
		return
	}
	bizType := c.Param("bizType")
	if bizType == "" || globalservice.GetGlobalManager().GetGlobalMetaCache().ServerConfig.EnableAuditFilter && bizType != "tidbcloud" {
		// FIXME(just1900): hard-code for now, should be removed after https://github.com/tidbcloud/tiinf/issues/127 gets resolved.
		// avoid polluting audit log with non-prod requests
		return
	}

	// main handler name in the form of "{pkg}.{HanlderName}"
	handlerFuncName := c.HandlerName()
	var audit *auditHelper
	for handler, a := range handlerToAuditHelpers {
		if strings.Contains(handlerFuncName, handler) {
			audit = a
			break
		}
	}
	if audit == nil {
		log.Debug("Skip Audit, as Handler is not registered")
		return
	}

	eventBuilder, err := buildAuditEventMeta(c, audit.event)
	if err != nil {
		log.Error("Failed to build audit event", zap.Error(err))
		return
	}

	details := auditlog.Details{
		auditlog.KeyError: keyErr,
	}
	if keyErr == nil {
		var result message.Result
		err = mapstructure.Decode(resp.Content, &result)
		if err != nil {
			log.Error("Failed to decode audit result", zap.Error(err))
		}
		audit.fn(result, details)
	}

	err = eventBuilder.SetResult(keyErr == nil).SetDetails(details.Build()).Submit()
	log.Info("Audit event submitted", zap.Any("event", audit.event), zap.Error(err))
}

func buildAuditEventMeta(c *gin.Context, event auditlog.EventType) (*auditlog.EventBuilder, error) {
	user, err := tigin.GetCurrentUser(c)
	if err != nil || user == nil {
		return nil, fmt.Errorf("user is not found %v", err)
	}
	orgID, err := strconv.ParseUint(c.Param("tenantID"), 10, 64)
	if err != nil {
		return nil, fmt.Errorf("orgID is not valid %v", err)
	}
	projectID, err := strconv.ParseUint(c.Param("projectID"), 10, 64)
	if err != nil {
		return nil, fmt.Errorf("projectID is not valid %v", err)
	}
	clusterID, err := strconv.ParseUint(c.Param("applicationID"), 10, 64)
	if err != nil {
		return nil, fmt.Errorf("clusterID is not valid %v", err)
	}
	return auditlog.NewEvent(c, event).ParseGIN(c).SetOperatorEmail(user.Email).SetOrgID(orgID).SetProjectID(projectID).SetClusterID(clusterID), nil
}

func EnrichUpdateAlertRulesDetail(result message.Result, details auditlog.Details) {
	var rulesResp message.UpdateAlertRulesByCloudUserResp
	err := mapstructure.Decode(result.Data, &rulesResp)
	if err != nil {
		log.Error("EnrichUpdateAlertRulesDetail", zap.Error(err))
		return
	}

	// we suppose only one rule is updated at a time
	for _, info := range rulesResp.Data {
		details["alertrule_id"] = info.AlertName
		details["alertrule_old_status"] = info.OldStatus
		details[auditlog.KeyAlertRuleNewStatus] = info.Status
		details[auditlog.KeyAlertRuleNewThreshold] = fmt.Sprintf("%.4f", info.Threshold)
		details[auditlog.KeyAlertRuleOldThreshold] = fmt.Sprintf("%.4f", info.OldThreshold)
		details[auditlog.KeyAlertRuleNewTriggerInterval] = utils.ConvertDurationString(info.For)
		details[auditlog.KeyAlertRuleOldTriggerInterval] = utils.ConvertDurationString(info.OldFor)
	}
}
