package alert

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

	"github.com/gin-gonic/gin"
	"github.com/prometheus/alertmanager/template"
	"go.uber.org/zap"

	"github.com/pingcap/log"
	"github.com/tidbcloud/tiinf/sdk/auditlog"

	"github.com/pingcap-inc/clinic_cloud_server/pkg/alert/notification"
	"github.com/pingcap-inc/clinic_cloud_server/pkg/common"
	"github.com/pingcap-inc/clinic_cloud_server/pkg/entity"
)

func getOrgIdAndClusterIdAndSubscriberIdFromPath(c *gin.Context) (uint64, uint64, uint, error) {
	tenantID, clusterID, err := getOrgIdAndClusterIdFromPath(c)
	if err != nil {
		return 0, 0, 0, err
	}
	subscriberIDStr := c.Param("alertSubscriberId")
	if subscriberIDStr == "" {
		return 0, 0, 0, fmt.Errorf("alertSubscriberId is required")
	}
	subscriberID, err := strconv.ParseUint(subscriberIDStr, 10, 64)
	if err != nil {
		return 0, 0, 0, fmt.Errorf("invalid alertSubscriberId")
	}
	return tenantID, clusterID, uint(subscriberID), nil
}

func getOrgIdAndClusterIdFromPath(c *gin.Context) (uint64, uint64, error) {
	tenantIDStr := c.Param("orgId")
	clusterIDStr := c.Param("clusterId")
	if tenantIDStr == "" {
		return 0, 0, fmt.Errorf("orgId is required")
	}
	if clusterIDStr == "" {
		return 0, 0, fmt.Errorf("clusterId is required")
	}
	tenantID, err := strconv.ParseUint(tenantIDStr, 10, 64)
	if err != nil {
		return 0, 0, fmt.Errorf("invalid orgId")
	}
	clusterID, err := strconv.ParseUint(clusterIDStr, 10, 64)
	if err != nil {
		return 0, 0, fmt.Errorf("invalid clusterId")
	}
	return tenantID, clusterID, nil
}

// ListNextGenAlertSubscribers
//
//	@Id				ListNextGenAlertSubscribers
//	@Tags			alert subscriber
//	@Description	list next-gen alert subscribers
//	@Produce		json
//	@Param			orgId		path		string	true	"organization id"
//	@Param			clusterId	path		string	true	"cluster id"
//	@Success		200			{object}	entity.ListAlertSubscribersResponse
//	@Failure		400			{object}	common.BaseResp
//	@Failure		500			{object}	common.BaseResp
//	@Router			/api/alert/v1/orgs/{orgId}/clusters/{clusterId}/alertSubscribers [get]
func (s *Service) ListNextGenAlertSubscribers(c *gin.Context) {
	tenantID, clusterID, err := getOrgIdAndClusterIdFromPath(c)
	if err != nil {
		common.FailWithCode(c, http.StatusBadRequest, err.Error())
		return
	}

	alertSubscribers, err := s.listAlertSubscribers(
		c, tenantID, clusterID, entity.ClusterDeployTypeNextGen, true)
	if err != nil {
		log.Error("list alert subscribers failed",
			zap.Error(err),
			zap.Uint64("tenant_id", tenantID),
			zap.Uint64("cluster_id", clusterID),
		)
		common.FailWithCode(c, http.StatusInternalServerError, "failed to list alert subscribers")
		return
	}
	common.Success(c, entity.ListAlertSubscribersResponse{
		AlertSubscribers: alertSubscribers,
		TotalSize:        len(alertSubscribers),
	})
}

// GetNextGenAlertSubscriberQuota
//
//	@Id				GetNextGenAlertSubscriberQuota
//	@Tags			alert subscriber
//	@Description	get next-gen alert subscriber quota
//	@Produce		json
//	@Param			orgId		path		string	true	"organization id"
//	@Param			clusterId	path		string	true	"cluster id"
//	@Success		200			{object}	entity.GetAlertSubscriberQuotaResponse
//	@Failure		400			{object}	common.BaseResp
//	@Failure		500			{object}	common.BaseResp
//	@Router			/api/alert/v1/orgs/{orgId}/clusters/{clusterId}/alertSubscriberQuota [get]
func (s *Service) GetNextGenAlertSubscriberQuota(c *gin.Context) {
	tenantID, _, err := getOrgIdAndClusterIdFromPath(c)
	if err != nil {
		common.FailWithCode(c, http.StatusBadRequest, err.Error())
		return
	}
	supportPlan, maxAlertSubscribersCount, err := s.getAlertSubscriberQuota(c, tenantID)
	if err != nil {
		log.Error("get alert subscriber quota failed",
			zap.Error(err),
			zap.Uint64("tenant_id", tenantID),
		)
		common.FailWithCode(c, http.StatusInternalServerError, "failed to get alert subscriber quota")
		return
	}

	common.Success(c, entity.GetAlertSubscriberQuotaResponse{
		MaxAlertSubscribersCount: maxAlertSubscribersCount,
		SupportPlan:              supportPlan,
	})
}

var nextGenDryRunAlert = template.Alert{
	Status: "firing",
	Labels: map[string]string{
		notification.AlertLabelSeverity: "info",
	},
	Annotations: map[string]string{
		notification.AlertAnnotationSubject: "Welcome to Alert Subscription on TiDB Cloud",
		notification.AlertAnnotationContent: `Welcome to the alert subscription feature on TiDB Cloud. This is a test alert notification to confirm that we can successfully send alert notifications to you.

Please confirm that you wish to subscribe to alerts for the [TiDBClusterName] cluster. This address will receive notifications whenever an alert is triggered within this cluster.

If everything looks correct, please remember to save the current alert subscriber setup on the page.

If you did not request this test or have any concerns, please contact our <a href="https://docs.pingcap.com/tidbcloud/tidb-cloud-support">support team</a>.`,
	},
	StartsAt: time.Now(),
}

// CreateNextGenAlertSubscriber
//
//	@Id				CreateNextGenAlertSubscriber
//	@Tags			alert subscriber
//	@Description	create next-gen alert subscriber
//	@Produce		json
//	@Param			orgId		path		string								true	"organization id"
//	@Param			clusterId	path		string								true	"cluster id"
//	@Param			req			body		entity.CreateAlertSubscriberRequest	true	"request param"
//	@Success		200			{object}	common.BaseResp
//	@Router			/api/alert/v1/orgs/{orgId}/clusters/{clusterId}/alertSubscribers [post]
func (s *Service) CreateNextGenAlertSubscriber(c *gin.Context) {
	tenantID, clusterID, err := getOrgIdAndClusterIdFromPath(c)
	if err != nil {
		common.FailWithCode(c, http.StatusBadRequest, err.Error())
		return
	}
	req := &entity.CreateAlertSubscriberRequest{}
	err = c.ShouldBindJSON(req)
	if err != nil {
		common.FailWithCode(c, http.StatusBadRequest, err.Error())
		return
	}
	logger := log.L().With(
		zap.Uint64("tenant_id", tenantID),
		zap.Uint64("cluster_id", clusterID),
		zap.String("subscriber_type", string(req.SubscriberType)),
		zap.String("subscriber_name", req.Name),
		zap.Bool("dry_run", req.DryRun),
	)

	alertSubscribers, err := s.listAlertSubscribers(
		c.Request.Context(), tenantID, clusterID, entity.ClusterDeployTypeNextGen, true)
	if err != nil {
		logger.Error("list alert subscribers failed", zap.Error(err))
		common.FailWithCode(c, http.StatusInternalServerError, "failed to list existent alert subscribers")
		return
	}
	item := entity.AlertSubscribersItem{
		SubscriberType: req.SubscriberType,
		Name:           req.Name,
		Email:          req.Email,
		SlackUrl:       req.SlackUrl,
		ZoomUrl:        req.ZoomUrl,
		ZoomToken:      req.ZoomToken,
		FlashDutyUrl:   req.FlashDutyUrl,
		PagerDutyKey:   req.PagerDutyKey,
		WebhookUrl:     req.WebhookUrl,
		LarkWebhookUrl: req.LarkWebhookUrl,
		LarkSecret:     req.LarkSecret,
	}

	supportPlan, maxAlertSubscribersCount, err := s.getAlertSubscriberQuota(c, tenantID)
	if err != nil {
		logger.Error("get alert subscriber quota failed", zap.Error(err))
		common.FailWithCode(c, http.StatusInternalServerError, "failed to get alert subscriber quota")
		return
	}
	if (item.SubscriberType != entity.AlertSubscriberTypeEmail) &&
		(supportPlan != entity.SupportPlanTypeEnterprise &&
			supportPlan != entity.SupportPlanTypePremium) {
		common.FailWithCode(c, http.StatusBadRequest, "need upgrade to enterprise or premium support plan")
		return
	}
	if len(alertSubscribers) >= maxAlertSubscribersCount {
		logger.Error("create alert subscriber failed, exceed the quota",
			zap.Int("count", len(alertSubscribers)),
		)
		common.FailWithCode(c, http.StatusBadRequest, "exceed the quota")
		return
	}

	if err := s.validateAlertSubscriber(
		c,
		logger,
		tenantID, clusterID,
		entity.ClusterDeployTypeNextGen,
		item); err != nil {
		logger.Error("validate alert subscriber failed", zap.Error(err))
		common.FailWithCode(c, http.StatusBadRequest, err.Error())
		return
	}

	if req.DryRun {
		err = s.dryRunAlertSubscriber(c, item, nextGenDryRunAlert, tenantID, clusterID, entity.ClusterDeployTypeNextGen)
		if err != nil {
			logger.Error("dry run alert subscriber failed", zap.Error(err))
			common.FailWithCode(c, http.StatusInternalServerError, "Test connection failed. Please check if the configuration is correct.")
			return
		}
		logger.Info("dry run alert subscriber success")
		common.Success(c, nil)
	} else {
		err = s.createOrUpdateAlertSubscriber(c,
			logger, tenantID, clusterID, entity.ClusterDeployTypeNextGen, item)
		details := auditlog.Details{
			// auditlog.KeyUserEmail:     req.Email,
			auditlog.KeyTargetClusterId: clusterID,
			auditlog.KeyError:           err,
		}
		auditErr := auditlog.
			NewEvent(c, auditlog.EventSubscribeAlerts).
			ParseGIN(c).
			SetOrgID(tenantID).
			SetResult(err == nil).
			SetDetails(details.Build()).
			Submit()
		if auditErr != nil {
			logger.Error("audit log failed", zap.Error(auditErr))
		}
		if err != nil {
			logger.Error("create alert subscriber failed", zap.Error(err))
			common.FailWithCode(c, http.StatusInternalServerError, err.Error())
			return
		}
		logger.Info("create alert subscriber success")
		common.Success(c, nil)
	}
}

// DeleteNextGenAlertSubscriber
//
//	@Id				DeleteNextGenAlertSubscriber
//	@Tags			alert subscriber
//	@Description	delete next-gen alert subscriber
//	@Produce		json
//	@Param			orgId				path		string	true	"organization id"
//	@Param			clusterId			path		string	true	"cluster id"
//	@Param			alertSubscriberId	path		string	true	"alert subscriber id"
//	@Success		200					{object}	common.BaseResp
//	@Failure		400					{object}	common.BaseResp
//	@Failure		500					{object}	common.BaseResp
//	@Router			/api/alert/v1/orgs/{orgId}/clusters/{clusterId}/alertSubscribers/{alertSubscriberId} [delete]
func (s *Service) DeleteNextGenAlertSubscriber(c *gin.Context) {
	tenantID, clusterID, subscriberID, err := getOrgIdAndClusterIdAndSubscriberIdFromPath(c)
	if err != nil {
		common.FailWithCode(c, http.StatusBadRequest, err.Error())
		return
	}
	logger := log.L().With(
		zap.Uint64("tenant_id", tenantID),
		zap.Uint64("cluster_id", clusterID),
		zap.Uint("subscriber_id", subscriberID),
	)
	err = s.deleteAlertSubscriber(c.Request.Context(),
		tenantID, clusterID,
		entity.ClusterDeployTypeNextGen,
		entity.AlertSubscribersItem{ID: subscriberID},
	)
	details := auditlog.Details{
		// auditlog.KeyUserEmail:     req.Email,
		auditlog.KeyTargetClusterId: clusterID,
		auditlog.KeyError:           err,
	}
	auditErr := auditlog.NewEvent(c, auditlog.EventUnsubscribeAlerts).
		ParseGIN(c).
		SetOrgID(tenantID).
		SetResult(err == nil).
		SetDetails(details.Build()).
		Submit()
	if auditErr != nil {
		logger.Error("audit log failed", zap.Error(auditErr))
	}
	if err != nil {
		logger.Error("delete alert subscriber failed",
			zap.Error(err),
		)
		common.FailWithCode(c, http.StatusInternalServerError, err.Error())
		return
	}
	logger.Info("delete alert subscriber success")
	common.Success(c, nil)
}
