package notification

import (
	"context"
	"encoding/json"
	"fmt"

	"github.com/google/uuid"
	"github.com/pingcap/log"
	"github.com/prometheus/alertmanager/template"
	account_pb "github.com/tidbcloud/account/idl/pbgen/proto/account"
	"go.uber.org/zap"

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

type EmailNotification struct {
	accountClient    *account.Client
	notificationRepo *repo.AlertNotificationRepo
}

func NewEmailNotification(
	accountClient *account.Client,
	notificationRepo *repo.AlertNotificationRepo,
) *EmailNotification {
	return &EmailNotification{
		accountClient:    accountClient,
		notificationRepo: notificationRepo,
	}
}

func (n *EmailNotification) Push(
	ctx context.Context,
	alert template.Alert,
	subscribers []entity.AlertSubscribersItem,
	alertMeta AlertMeta,
	dryRun bool,
) error {
	return n.handle(ctx, alert, subscribers, alertMeta, dryRun)
}

func (n *EmailNotification) PushAsync(
	ctx context.Context,
	alert template.Alert,
	subscribers []entity.AlertSubscribersItem,
	alertMeta AlertMeta,
	dryRun bool,
) {
	go func() {
		if err := n.handle(ctx, alert, subscribers, alertMeta, dryRun); err != nil {
			log.Error("send email notification failed",
				zap.Uint64("tenant_id", alertMeta.TenantId),
				zap.Uint64("cluster_id", alertMeta.ClusterId),
				zap.Bool("dry_run", dryRun),
				zap.Error(err),
			)
		}
	}()
}

func (n *EmailNotification) handle(
	ctx context.Context,
	alert template.Alert,
	subscribers []entity.AlertSubscribersItem,
	alertMeta AlertMeta,
	dryRun bool,
) error {
	emails := make([]string, 0, len(subscribers))
	notificationRecords := make([]*db.AlertNotification, 0, len(emails))
	for _, subscriber := range subscribers {
		if subscriber.SubscriberType != entity.AlertSubscriberTypeEmail {
			continue
		}
		if len(subscriber.Email) == 0 {
			continue
		}
		emails = append(emails, subscriber.Email)
		notificationRecord := &db.AlertNotification{
			TenantID:     alertMeta.TenantId,
			ProjectID:    alertMeta.ProjectId,
			ClusterID:    alertMeta.ClusterId,
			SubscriberID: uint64(subscriber.ID),
			Status:       "start",
		}
		notificationRecords = append(notificationRecords, notificationRecord)
	}
	if len(emails) == 0 {
		return nil
	}

	logger := log.L().With(
		zap.Uint64("tenant_id", alertMeta.TenantId),
		zap.Uint64("cluster_id", alertMeta.ClusterId),
		zap.Strings("emails", emails),
		zap.Bool("dry_run", dryRun),
	)
	logger.Info("start to send email notification")
	alertStr, err := json.Marshal(alert)
	if err != nil {
		return fmt.Errorf("failed to marshal alert, %w", err)
	}
	for _, notificationRecord := range notificationRecords {
		notificationRecord.AlertContent = string(alertStr)
	}
	batchUpdateNotificationStatus(ctx, logger,
		n.notificationRepo,
		notificationRecords,
		"start", "")
	defer func() {
		if err != nil {
			batchUpdateNotificationStatus(ctx, logger,
				n.notificationRepo,
				notificationRecords,
				"failure", err.Error())
		}
	}()

	subject := alert.Annotations[AlertAnnotationSubject]
	content := alert.Annotations[AlertAnnotationContent]
	if len(subject) == 0 {
		return fmt.Errorf("empty subject")
	}
	if len(content) == 0 {
		return fmt.Errorf("empty content")
	}
	starts := alert.StartsAt
	content = replaceEmailLineBreak(replaceEmailMessage(
		content,
		alert,
		alertMeta.ProjectName,
		alertMeta.ClusterName))

	// SendEmail with account grpc
	var vars map[string]string
	if dryRun {
		vars = map[string]string{
			"subject": subject,
			"content": content,
		}
	} else {
		vars = map[string]string{
			"subject": fmt.Sprintf("[%s] %s at %s", alert.Status, subject, starts.Format(alertTimeFormatShort)),
			"content": fmt.Sprintf("Status: %s <br />Alert time: %s <br />Message: %s", alert.Status, starts.Format(alertTimeFormatFull), content),
		}
	}
	resp, err := n.accountClient.SendEmail(ctx, &account_pb.SendEmailReq{
		OrgId:        alertMeta.TenantId,
		Scene:        NotificationSceneUserAlert,
		Identifier:   uuid.New().String(),
		Subject:      subject,
		Emails:       emails,
		TemplateId:   "d-bcd3631c2d0240a3a0502ef107ccc2c0",
		TemplateVars: vars,
	})
	if resp.BaseResp.ErrCode != 0 {
		return fmt.Errorf("send email failed, code=%d err=%s", resp.BaseResp.ErrCode, resp.BaseResp.ErrMsg)
	}
	if err != nil {
		return fmt.Errorf("send email failed, %w", err)
	}

	batchUpdateNotificationStatus(ctx, logger, n.notificationRepo, notificationRecords, "success", "")
	logger.Info("send email notification success")
	return nil
}

func batchUpdateNotificationStatus(
	ctx context.Context,
	logger *zap.Logger,
	notificationRepo *repo.AlertNotificationRepo,
	notificationRecords []*db.AlertNotification,
	status string,
	message string,
) {
	for i, notificationRecord := range notificationRecords {
		notificationRecord := notificationRecord
		notificationRecord.Status = status
		if message != "" {
			notificationRecord.Message = message
		}
		notificationRecords[i] = notificationRecord
	}
	err := notificationRepo.BatchSave(ctx, notificationRecords)
	if err != nil {
		logger.Warn("update alert notification status failed",
			zap.String("status", status),
			zap.Error(err))
	}
}
