package sendgridcli

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/sendgrid/sendgrid-go"
	"github.com/sendgrid/sendgrid-go/helpers/mail"
	"github.com/tidbcloud/devops/pkg/infra/config"
)

type SendBulkPersonalizedEmailsRequest struct {
	From         string
	To           []string
	TemplateID   string
	TemplateVars map[string]string
	DeliveryTime time.Time
}

// MaxBulkPersonalizedEmailRecipients is the maximum number of recipients accepted by one bulk request.
const MaxBulkPersonalizedEmailRecipients = 1000

type SendPersonalizedEmailRequest struct {
	From         string
	To           []string
	CC           []string
	BCC          []string
	TemplateID   string
	TemplateVars map[string]string
	DeliveryTime time.Time
}

type SendPlainEmailRequest struct {
	From     string
	FromName string
	To       string
	CC       []string
	Subject  string
	HTMLBody string
}

type SendPlainEmailResponse struct {
	MessageID  string
	StatusCode int
}

type Client interface {
	// SendBulkPersonalizedEmails sends a separate personalized email to each To recipient.
	// To recipients cannot see one another because each recipient uses an independent personalization.
	SendBulkPersonalizedEmails(ctx context.Context, req SendBulkPersonalizedEmailsRequest) error
	// SendPersonalizedEmail sends one personalized email containing all To recipients.
	// To recipients can see one another because they share the same personalization.
	SendPersonalizedEmail(ctx context.Context, req SendPersonalizedEmailRequest) error
	SendPlainEmail(ctx context.Context, req SendPlainEmailRequest) (*SendPlainEmailResponse, error)
}

type client struct {
	raw *sendgrid.Client
}

func NewClient(conf *config.RootConfig) (Client, error) {
	sgCli := sendgrid.NewSendClient(conf.Clients.SendGrid.APIKey)
	return &client{
		raw: sgCli,
	}, nil
}

func plainEmailFrom(req SendPlainEmailRequest) *mail.Email {
	fromName := strings.TrimSpace(req.FromName)

	return mail.NewEmail(fromName, req.From)
}

func (c *client) SendBulkPersonalizedEmails(ctx context.Context, req SendBulkPersonalizedEmailsRequest) error {
	if len(req.To) > MaxBulkPersonalizedEmailRecipients {
		return fmt.Errorf("too many recipients: got %d, maximum is %d", len(req.To), MaxBulkPersonalizedEmailRecipients)
	}

	message := mail.NewV3Mail()
	message.SetFrom(mail.NewEmail("", req.From))

	for _, address := range req.To {
		address = strings.TrimSpace(address)
		if address == "" {
			continue
		}

		personalization := mail.NewPersonalization()
		personalization.AddTos(mail.NewEmail("", address))

		for key, value := range req.TemplateVars {
			personalization.SetDynamicTemplateData(key, value)
		}

		message.AddPersonalizations(personalization)
	}

	message.SetTrackingSettings(mail.NewTrackingSettings().SetOpenTracking(mail.NewOpenTrackingSetting().SetEnable(true)))
	message.SetSendAt(int(req.DeliveryTime.Unix()))
	message.SetTemplateID(req.TemplateID)

	resp, err := c.raw.Send(message)
	if err != nil {
		return err
	}
	if resp.StatusCode >= 400 {
		return fmt.Errorf("send mail failed, code=%d body=%s", resp.StatusCode, resp.Body)
	}
	return nil
}

func (c *client) SendPersonalizedEmail(ctx context.Context, req SendPersonalizedEmailRequest) error {
	fromEmail := mail.NewEmail("", req.From)
	toEmails := make([]*mail.Email, 0, len(req.To))
	ccEmails := make([]*mail.Email, 0, len(req.CC))
	bccEmails := make([]*mail.Email, 0, len(req.BCC))

	for _, to := range req.To {
		if to != "" {
			toEmails = append(toEmails, mail.NewEmail("", to))
		}
	}
	for _, cc := range req.CC {
		if cc != "" {
			ccEmails = append(ccEmails, mail.NewEmail("", cc))
		}
	}
	for _, bcc := range req.BCC {
		if bcc != "" {
			bccEmails = append(bccEmails, mail.NewEmail("", bcc))
		}
	}

	// build personalization
	personalize := mail.NewPersonalization()
	personalize.AddTos(toEmails...)
	personalize.AddCCs(ccEmails...)
	personalize.AddBCCs(bccEmails...)

	for k, v := range req.TemplateVars {
		personalize.SetDynamicTemplateData(k, v)
	}

	// build message
	message := mail.NewV3Mail()
	message.SetFrom(fromEmail)
	message.AddPersonalizations(personalize)
	message.SetTrackingSettings(mail.NewTrackingSettings().SetOpenTracking(mail.NewOpenTrackingSetting().SetEnable(true)))
	message.SetSendAt(int(req.DeliveryTime.Unix()))
	message.SetTemplateID(req.TemplateID)

	// send
	resp, err := c.raw.Send(message)
	if err != nil {
		return err
	}
	if resp.StatusCode >= 400 {
		return fmt.Errorf("send mail failed, code=%d body=%s", resp.StatusCode, resp.Body)
	}
	return nil
}

func (c *client) SendPlainEmail(ctx context.Context, req SendPlainEmailRequest) (*SendPlainEmailResponse, error) {
	from := plainEmailFrom(req)
	to := mail.NewEmail("", req.To)

	message := mail.NewSingleEmail(from, req.Subject, to, "", req.HTMLBody)

	// Add CC recipients
	if len(req.CC) > 0 {
		personalization := mail.NewPersonalization()
		personalization.AddTos(to)
		for _, cc := range req.CC {
			if cc != "" {
				personalization.AddCCs(mail.NewEmail("", cc))
			}
		}
		message.Personalizations = []*mail.Personalization{personalization}
	}

	// Send via SendGrid API
	resp, err := c.raw.SendWithContext(ctx, message)
	if err != nil {
		return nil, fmt.Errorf("SendGrid API error: %w", err)
	}

	if resp.StatusCode >= 400 {
		return nil, fmt.Errorf("SendGrid returned error status %d: %s",
			resp.StatusCode, resp.Body)
	}

	// Extract X-Message-ID from response headers
	messageID := ""
	if msgIDs, ok := resp.Headers["X-Message-Id"]; ok && len(msgIDs) > 0 {
		messageID = msgIDs[0]
	}

	return &SendPlainEmailResponse{
		MessageID:  messageID,
		StatusCode: resp.StatusCode,
	}, nil
}
