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"
)

const (
	pagerDutyEventsAPIEndpoint   = "https://events.pagerduty.com/v2/enqueue"
	pagerDutyVariableRoutingKey  = "$ROUTING_KEY"
	pagerDutyVariableEventAction = "$EVENT_ACTION"
)

var PagerDutyPayload = `{
  "routing_key": "$ROUTING_KEY",
  "event_action": "$EVENT_ACTION",
  "dedup_key": "$CLUSTER_NAME:$ALERT_NAME",
  "payload": {
    "summary": "$ALERT_NAME",
    "source": "TiDB Cloud",
    "severity": "$SEVERITY",
    "timestamp": "$STARTS_AT",
    "custom_details": {
      "message": "$MESSAGE",
      "project": "$PROJECT_NAME",
      "cluster": "$CLUSTER_NAME"
    }
  }
}`

var DryRunPagerDutyPayload = `{
  "routing_key": "$ROUTING_KEY",
  "event_action": "trigger",
  "dedup_key": "$ALERT_NAME",
  "payload": {
    "summary": "[$SEVERITY] $ALERT_NAME",
    "source": "TiDB Cloud",
    "severity": "$SEVERITY",
	"timestamp": "$STARTS_AT",
    "custom_details": {
      "message": "$MESSAGE"
    }
  }
}`

type PagerDutyNotification struct {
	notificationRepo *repo.AlertNotificationRepo
}

func (n *PagerDutyNotification) Push(
	ctx context.Context,
	alert template.Alert,
	subscribers []entity.AlertSubscribersItem,
	alertMeta AlertMeta,
	dryRun bool,
) error {
	for _, subscriber := range subscribers {
		if subscriber.SubscriberType != entity.AlertSubscriberTypePagerDuty {
			continue
		}
		var err error
		var retry bool
		for retryTimes := 0; retryTimes < 3; retryTimes++ {
			retry, err = n.handle(ctx, alert, subscriber, alertMeta, dryRun)
			if err != nil {
				log.Error("send pagerduty 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 *PagerDutyNotification) PushAsync(
	ctx context.Context,
	alert template.Alert,
	subscribers []entity.AlertSubscribersItem,
	alertMeta AlertMeta,
	dryRun bool,
) {
	for _, subscriber := range subscribers {
		if subscriber.SubscriberType != entity.AlertSubscriberTypePagerDuty {
			continue
		}
		go func(subscriber entity.AlertSubscribersItem) {
			for retryTimes := 0; retryTimes < 3; retryTimes++ {
				retry, err := n.handle(ctx, alert, subscriber, alertMeta, dryRun)
				if err != nil {
					log.Error("send pagerduty 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 dryRun || !retry {
					break
				}
			}
		}(subscriber)
	}
}

func (n *PagerDutyNotification) 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.Bool("dry_run", dryRun),
	)
	logger.Info("start to send pagerduty 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}
	basePayload := PagerDutyPayload
	if dryRun {
		basePayload = DryRunPagerDutyPayload
	}
	// Replace placeholders first, then inject routing_key.
	payload := replaceWebhookPayload(
		basePayload,
		alert,
		alertMeta.ProjectName,
		alertMeta.ClusterName,
	)
	payload = strings.ReplaceAll(payload, pagerDutyVariableRoutingKey,
		escapeString(subscriber.PagerDutyKey))

	// Map Alertmanager status to pagerduty event_action.
	// "trigger" creates/incidents, "resolve" resolves the incident for the same dedup_key.
	event_action := "trigger"
	if strings.ToLower(alert.Status) == "resolved" {
		event_action = "resolve"
	}
	payload = strings.ReplaceAll(payload, pagerDutyVariableEventAction, event_action)

	req, err := http.NewRequest("POST", pagerDutyEventsAPIEndpoint, strings.NewReader(payload))
	if err != nil {
		return false, fmt.Errorf("failed to create http request, %w", err)
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		return true, fmt.Errorf("send to pagerduty failed, err: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusAccepted {
		bodyBytes, readErr := io.ReadAll(resp.Body)
		if readErr != nil {
			return IsRetry(resp.StatusCode), fmt.Errorf("send pagerduty notification failed, status code: %d, payload: %s, response: %w", resp.StatusCode, payload, readErr)
		}
		return IsRetry(resp.StatusCode), fmt.Errorf("send pagerduty notification failed, status code: %d, payload: %s, response: %s", resp.StatusCode, payload, string(bodyBytes))
	}

	updateNotificationStatus(ctx, logger, n.notificationRepo, notificationRecord, "success", "")
	logger.Info("send pagerduty notification success")
	return false, nil
}
