package email

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const (
	defaultCloudflareAPIURL = "https://api.cloudflare.com/client/v4"
	cloudflareTimeout       = 15 * time.Second
)

// CloudflareSender sends transactional email through Cloudflare Email
// Service's REST API. It is intended for deployments outside Workers.
type CloudflareSender struct {
	accountID string
	apiToken  string
	apiURL    string
	from      string
	client    *http.Client
	logger    *slog.Logger
}

func NewCloudflareSender(accountID, apiToken, from, apiURL string, logger *slog.Logger) *CloudflareSender {
	if apiURL == "" {
		apiURL = defaultCloudflareAPIURL
	}
	if logger == nil {
		logger = slog.Default()
	}
	return &CloudflareSender{
		accountID: accountID,
		apiToken:  apiToken,
		apiURL:    strings.TrimRight(apiURL, "/"),
		from:      from,
		client: &http.Client{
			Timeout: cloudflareTimeout,
			CheckRedirect: func(req *http.Request, via []*http.Request) error {
				return http.ErrUseLastResponse
			},
		},
		logger: logger,
	}
}

type cloudflareRequest struct {
	From    string   `json:"from"`
	To      []string `json:"to"`
	Subject string   `json:"subject"`
	Text    string   `json:"text"`
}

type cloudflareResponse struct {
	Success bool `json:"success"`
	Errors  []struct {
		Code    int    `json:"code"`
		Message string `json:"message"`
	} `json:"errors"`
	Result *struct {
		Delivered        []string `json:"delivered"`
		MessageID        string   `json:"message_id"`
		PermanentBounces []string `json:"permanent_bounces"`
		Queued           []string `json:"queued"`
	} `json:"result"`
}

func (c *CloudflareSender) Send(ctx context.Context, to []string, subject, body string) error {
	if c.accountID == "" {
		return &PermanentError{Cause: fmt.Errorf("cloudflare account id not configured")}
	}
	if c.apiToken == "" {
		return &PermanentError{Cause: fmt.Errorf("cloudflare API token not configured")}
	}
	if c.from == "" {
		return &PermanentError{Cause: fmt.Errorf("cloudflare from address not configured")}
	}
	if len(to) == 0 {
		return &PermanentError{Cause: fmt.Errorf("cloudflare send requires at least one recipient")}
	}

	payload, err := json.Marshal(cloudflareRequest{
		From:    c.from,
		To:      to,
		Subject: subject,
		Text:    body,
	})
	if err != nil {
		return fmt.Errorf("marshal cloudflare email request: %w", err)
	}

	endpoint := fmt.Sprintf("%s/accounts/%s/email/sending/send", c.apiURL, url.PathEscape(c.accountID))
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(payload))
	if err != nil {
		return fmt.Errorf("build cloudflare email request: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+c.apiToken)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")

	resp, err := c.client.Do(req)
	if err != nil {
		return &TemporaryError{Cause: fmt.Errorf("cloudflare email request: %w", err)}
	}
	defer resp.Body.Close()

	responseBody, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<14))
	if readErr != nil {
		return &TemporaryError{Cause: fmt.Errorf("read cloudflare email response: %w", readErr)}
	}
	if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= http.StatusInternalServerError {
		return &TemporaryError{Cause: fmt.Errorf("cloudflare email server error: status=%d body=%s", resp.StatusCode, truncate(string(responseBody)))}
	}
	if resp.StatusCode != http.StatusOK {
		return &PermanentError{Cause: fmt.Errorf("cloudflare email rejected: status=%d body=%s", resp.StatusCode, truncate(string(responseBody)))}
	}

	var parsed cloudflareResponse
	if err := json.Unmarshal(responseBody, &parsed); err != nil {
		return fmt.Errorf("decode cloudflare email response: %w", err)
	}
	if !parsed.Success {
		return &PermanentError{Cause: fmt.Errorf("cloudflare email rejected: %s", cloudflareErrors(parsed.Errors))}
	}
	if parsed.Result == nil {
		return fmt.Errorf("cloudflare email response missing result")
	}
	if len(parsed.Result.PermanentBounces) > 0 {
		return &PermanentError{Cause: fmt.Errorf("cloudflare email permanently bounced for %d recipient(s)", len(parsed.Result.PermanentBounces))}
	}

	c.logger.Info("cloudflare email accepted",
		"message_id", parsed.Result.MessageID,
		"delivered", len(parsed.Result.Delivered),
		"queued", len(parsed.Result.Queued),
		"subject", subject,
	)
	return nil
}

func cloudflareErrors(errors []struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}) string {
	if len(errors) == 0 {
		return "unknown API error"
	}
	parts := make([]string, 0, len(errors))
	for _, apiErr := range errors {
		parts = append(parts, fmt.Sprintf("code=%d: %s", apiErr.Code, apiErr.Message))
	}
	return strings.Join(parts, "; ")
}
