package notification

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	"github.com/pingcap/log"
	"github.com/prometheus/alertmanager/template"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic_cloud_server/pkg/alert/repo"
	"github.com/pingcap-inc/clinic_cloud_server/pkg/db"
	"github.com/pingcap-inc/clinic_cloud_server/pkg/entity"
)

var ZoomPayload = `{
    "is_markdown_support": true,
    "content": {
        "head": {
            "text": "[$SEVERITY] $ALERT_NAME"
        },
        "body": [
            {
                "type": "fields",
                "items": [
                    {
                        "key": "Status",
                        "value": "$STATUS"
                    },
                    {
                        "key": "Cluster Name",
                        "value": "$CLUSTER_NAME"
                    },
                    {
                        "key": "StartAt",
                        "value": "$STARTS_AT"
                    },
                    {
                        "key": "Message",
                        "value": "$MESSAGE"
                    }
                ]
            }
        ]
    }
}`

var DryRunZoomPayload = `{
    "is_markdown_support": true,
    "content": {
        "head": {
            "text": "[$SEVERITY] $ALERT_NAME"
        },
        "body": [
            {
                "type": "fields",
                "items": [
                    {
                        "key": "Message",
                        "value": "$MESSAGE"
                    }
                ]
            }
        ]
    }
}`

type ZoomNotification struct {
	notificationRepo *repo.AlertNotificationRepo
}

func (n *ZoomNotification) Push(
	ctx context.Context,
	alert template.Alert,
	subscribers []entity.AlertSubscribersItem,
	alertMeta AlertMeta,
	dryRun bool,
) error {
	for _, subscriber := range subscribers {
		if subscriber.SubscriberType != entity.AlertSubscriberTypeZoom {
			continue
		}
		var err error
		var retry bool
		// retry 3 times if failed
		for retryTimes := 0; retryTimes < 3; retryTimes++ {
			retry, err = n.handle(ctx, alert, subscriber, alertMeta, dryRun)
			if err != nil {
				log.Error("send zoom notification failed",
					zap.Uint64("tenant_id", alertMeta.TenantId),
					zap.Uint64("cluster_id", alertMeta.ClusterId),
					zap.Uint("subscriber_id", subscriber.ID),
					zap.Bool("dry_run", dryRun),
					zap.Int("retry_times", retryTimes),
					zap.Error(err))
			}
			// if success or dryRun, break
			if dryRun || !retry {
				break
			}
		}
		if err != nil {
			return err
		}
	}
	return nil
}

func (n *ZoomNotification) PushAsync(
	ctx context.Context,
	alert template.Alert,
	subscribers []entity.AlertSubscribersItem,
	alertMeta AlertMeta,
	dryRun bool,
) {
	for _, subscriber := range subscribers {
		if subscriber.SubscriberType != entity.AlertSubscriberTypeZoom {
			continue
		}
		go func(subscriber entity.AlertSubscribersItem) {
			// retry 3 times if failed
			for retryTimes := 0; retryTimes < 3; retryTimes++ {
				retry, err := n.handle(ctx, alert, subscriber, alertMeta, dryRun)
				if err != nil {
					log.Error("send zoom notification failed",
						zap.Uint64("tenant_id", alertMeta.TenantId),
						zap.Uint64("cluster_id", alertMeta.ClusterId),
						zap.Uint("subscriber_id", subscriber.ID),
						zap.Bool("dry_run", dryRun),
						zap.Int("retry_times", retryTimes),
						zap.Error(err))
				}
				// if success or dryRun, break
				if dryRun || !retry {
					break
				}
			}
		}(subscriber)
	}
}

func (n *ZoomNotification) handle(
	ctx context.Context,
	alert template.Alert,
	subscriber entity.AlertSubscribersItem,
	alertMeta AlertMeta,
	dryRun bool,
) (retry bool, err error) {
	logger := log.L().With(
		zap.Uint64("tenant_id", alertMeta.TenantId),
		zap.Uint64("cluster_id", alertMeta.ClusterId),
		zap.Uint("subscriber_id", subscriber.ID),
		zap.String("zoom_url", subscriber.ZoomUrl),
		zap.Bool("dry_run", dryRun),
	)
	logger.Info("start to send zoom notification")
	alertStr, err := json.Marshal(alert)
	if err != nil {
		return false, fmt.Errorf("failed to marshal alert, %w", err)
	}
	notificationRecord := &db.AlertNotification{
		TenantID:     alertMeta.TenantId,
		ProjectID:    alertMeta.ProjectId,
		ClusterID:    alertMeta.ClusterId,
		SubscriberID: uint64(subscriber.ID),
		AlertContent: string(alertStr),
		Status:       "start",
	}
	updateNotificationStatus(ctx, logger, n.notificationRepo, notificationRecord, "start", "")
	defer func() {
		if err != nil {
			updateNotificationStatus(ctx, logger, n.notificationRepo, notificationRecord, "failure", err.Error())
		}
	}()

	client := http.Client{
		Timeout: 15 * time.Second,
	}
	zoomPayload := ZoomPayload
	if dryRun {
		zoomPayload = DryRunZoomPayload
	}
	payload := replaceWebhookPayload(
		zoomPayload,
		alert,
		alertMeta.ProjectName,
		alertMeta.ClusterName,
	)
	payload = replaceHtmlLink(payload)
	req, err := http.NewRequest("POST", subscriber.ZoomUrl, strings.NewReader(payload))
	if err != nil {
		return false, fmt.Errorf("failed to create http request, %w", err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", subscriber.ZoomToken)
	resp, err := client.Do(req)
	if err != nil {
		return true, fmt.Errorf("send zoom notification failed, %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		bodyBytes, err := io.ReadAll(resp.Body)
		if err != nil {
			return IsRetry(resp.StatusCode), fmt.Errorf("send zoom notification failed, status code: %d, response: %w", resp.StatusCode, err)
		}
		return IsRetry(resp.StatusCode), fmt.Errorf("send zoom notification failed, status code: %d, response: %s", resp.StatusCode, string(bodyBytes))
	}

	updateNotificationStatus(ctx, logger, n.notificationRepo, notificationRecord, "success", "")
	log.Info("send zoom notification success",
		zap.Uint64("tenant_id", alertMeta.TenantId),
		zap.Uint("subscriber_id", subscriber.ID),
		zap.String("url", subscriber.ZoomUrl),
	)
	return false, nil
}
