package mail

import (
	"encoding/json"
	"mime"
	"net"
	"net/mail"
	"net/url"
	"strconv"
	"strings"
	"time"
	"unicode"

	"github.com/google/uuid"
	"github.com/ultisuite/ulti-backend/internal/api/apivalidate"
	"github.com/ultisuite/ulti-backend/internal/mail/limits"
	mailoauth "github.com/ultisuite/ulti-backend/internal/mail/oauth"
)

func validateAccountUUID(accountID string) *apivalidate.FieldDetail {
	if _, err := uuid.Parse(accountID); err != nil {
		return &apivalidate.FieldDetail{Field: "account_id", Message: "invalid"}
	}
	return nil
}

const (
	maxAccountRequestBody = 32 << 10  // 32 KiB
	maxWebhookRequestBody = 128 << 10 // 128 KiB
	maxRulesRequestBody   = 256 << 10 // 256 KiB
	maxFlagsLabelsBody    = 32 << 10  // 32 KiB
	maxOutboxScheduleBody = 4 << 10   // 4 KiB

	maxWebhookBodyTemplate = 64 << 10 // 64 KiB
	maxWebhookHeaders      = 20
	maxHeaderNameLen       = 256
	maxHeaderValueLen      = 8192
	maxWebhookSecretLen    = 512
	defaultWebhookRetries  = 3
	maxWebhookRetries      = 10

	maxSubjectLen  = 998
	maxEmailLen    = 320
	maxHostLen     = 253
	maxAccountName = 128
	maxUsernameLen = 256
	maxPasswordLen = 256
	maxWebhookName = 128
	maxRuleName    = 128
)

var allowedWebhookMethods = map[string]struct{}{
	"POST":  {},
	"PUT":   {},
	"PATCH": {},
}

func containsNewline(s string) bool {
	return strings.ContainsAny(s, "\r\n")
}

func validateEmailField(field, addr string) *apivalidate.FieldDetail {
	addr = strings.TrimSpace(addr)
	if addr == "" {
		return &apivalidate.FieldDetail{Field: field, Message: "required"}
	}
	if len(addr) > maxEmailLen {
		return &apivalidate.FieldDetail{Field: field, Message: "too long"}
	}
	if containsNewline(addr) {
		return &apivalidate.FieldDetail{Field: field, Message: "invalid"}
	}
	parsed, err := mail.ParseAddress(addr)
	if err != nil || parsed.Address == "" {
		return &apivalidate.FieldDetail{Field: field, Message: "invalid"}
	}
	return nil
}

func validateHostField(field, host string) *apivalidate.FieldDetail {
	host = strings.TrimSpace(host)
	if host == "" {
		return &apivalidate.FieldDetail{Field: field, Message: "required"}
	}
	if len(host) > maxHostLen {
		return &apivalidate.FieldDetail{Field: field, Message: "too long"}
	}
	if containsNewline(host) || strings.Contains(host, " ") {
		return &apivalidate.FieldDetail{Field: field, Message: "invalid"}
	}
	if ip := net.ParseIP(host); ip != nil {
		return nil
	}
	if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
		inner := strings.TrimPrefix(strings.TrimSuffix(host, "]"), "[")
		if ip := net.ParseIP(inner); ip != nil {
			return nil
		}
	}
	if !isDNSHostname(host) {
		return &apivalidate.FieldDetail{Field: field, Message: "invalid"}
	}
	return nil
}

func isDNSHostname(host string) bool {
	if strings.HasSuffix(host, ".") {
		host = strings.TrimSuffix(host, ".")
	}
	if host == "" {
		return false
	}
	labels := strings.Split(host, ".")
	for _, label := range labels {
		if len(label) == 0 || len(label) > 63 {
			return false
		}
		if label[0] == '-' || label[len(label)-1] == '-' {
			return false
		}
		for _, r := range label {
			if !(unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-') {
				return false
			}
		}
	}
	return true
}

func validatePortField(field string, port int) *apivalidate.FieldDetail {
	if port < 1 || port > 65535 {
		return &apivalidate.FieldDetail{Field: field, Message: "must be between 1 and 65535"}
	}
	return nil
}

func validateCredentialField(field, value string, maxLen int) *apivalidate.FieldDetail {
	if strings.TrimSpace(value) == "" {
		return &apivalidate.FieldDetail{Field: field, Message: "required"}
	}
	if len(value) > maxLen {
		return &apivalidate.FieldDetail{Field: field, Message: "too long"}
	}
	if containsNewline(value) {
		return &apivalidate.FieldDetail{Field: field, Message: "invalid"}
	}
	return nil
}

type createAccountRequest struct {
	Name     string `json:"name"`
	Email    string `json:"email"`
	Provider string `json:"provider"`
	IMAPHost string `json:"imap_host"`
	IMAPPort int    `json:"imap_port"`
	IMAPTLS  bool   `json:"imap_tls"`
	SMTPHost string `json:"smtp_host"`
	SMTPPort int    `json:"smtp_port"`
	SMTPTLS  bool   `json:"smtp_tls"`
	Username string `json:"username"`
	Password string `json:"password"`
}

type updateAccountRequest struct {
	Name     string `json:"name"`
	Email    string `json:"email"`
	Provider string `json:"provider"`
	IMAPHost string `json:"imap_host"`
	IMAPPort int    `json:"imap_port"`
	IMAPTLS  bool   `json:"imap_tls"`
	SMTPHost string `json:"smtp_host"`
	SMTPPort int    `json:"smtp_port"`
	SMTPTLS  bool   `json:"smtp_tls"`
	Username string `json:"username"`
	Password string `json:"password"`
}

func validateUpdateAccount(req *updateAccountRequest) *apivalidate.ValidationError {
	var details []apivalidate.FieldDetail
	if req.Name != "" && len(req.Name) > maxAccountName {
		details = append(details, apivalidate.FieldDetail{Field: "name", Message: "too long"})
	}
	if d := validateEmailField("email", req.Email); d != nil {
		details = append(details, *d)
	}
	if d := validateHostField("imap_host", req.IMAPHost); d != nil {
		details = append(details, *d)
	}
	if d := validateHostField("smtp_host", req.SMTPHost); d != nil {
		details = append(details, *d)
	}
	if d := validatePortField("imap_port", req.IMAPPort); d != nil {
		details = append(details, *d)
	}
	if d := validatePortField("smtp_port", req.SMTPPort); d != nil {
		details = append(details, *d)
	}
	if u := strings.TrimSpace(req.Username); u != "" {
		if d := validateCredentialField("username", u, maxUsernameLen); d != nil {
			details = append(details, *d)
		}
	}
	if req.Password != "" {
		if d := validateCredentialField("password", req.Password, maxPasswordLen); d != nil {
			details = append(details, *d)
		}
	}
	if len(details) == 0 {
		return nil
	}
	return apivalidate.NewValidationError(details...)
}

func validateCreateAccount(req *createAccountRequest) *apivalidate.ValidationError {
	var details []apivalidate.FieldDetail
	if req.Name != "" && len(req.Name) > maxAccountName {
		details = append(details, apivalidate.FieldDetail{Field: "name", Message: "too long"})
	}
	if d := validateEmailField("email", req.Email); d != nil {
		details = append(details, *d)
	}
	if d := validateHostField("imap_host", req.IMAPHost); d != nil {
		details = append(details, *d)
	}
	if d := validateHostField("smtp_host", req.SMTPHost); d != nil {
		details = append(details, *d)
	}
	if d := validatePortField("imap_port", req.IMAPPort); d != nil {
		details = append(details, *d)
	}
	if d := validatePortField("smtp_port", req.SMTPPort); d != nil {
		details = append(details, *d)
	}
	if d := validateCredentialField("username", req.Username, maxUsernameLen); d != nil {
		details = append(details, *d)
	}
	if d := validateCredentialField("password", req.Password, maxPasswordLen); d != nil {
		details = append(details, *d)
	}
	if len(details) == 0 {
		return nil
	}
	return apivalidate.NewValidationError(details...)
}

type testAccountRequest struct {
	AccountID     string `json:"account_id"`
	IMAPHost      string `json:"imap_host"`
	IMAPPort      int    `json:"imap_port"`
	IMAPTLS       bool   `json:"imap_tls"`
	SMTPHost      string `json:"smtp_host"`
	SMTPPort      int    `json:"smtp_port"`
	SMTPTLS       bool   `json:"smtp_tls"`
	Username      string `json:"username"`
	Password      string `json:"password"`
	AuthType      string `json:"auth_type"`
	AccessToken   string `json:"access_token"`
	OAuthProvider string `json:"oauth_provider"`
}

func validateTestAccount(req *testAccountRequest) *apivalidate.ValidationError {
	var details []apivalidate.FieldDetail
	useStoredCreds := strings.TrimSpace(req.AccountID) != ""

	if useStoredCreds {
		if d := validateAccountUUID(req.AccountID); d != nil {
			d.Field = "account_id"
			details = append(details, *d)
		}
	}

	if d := validateHostField("imap_host", req.IMAPHost); d != nil {
		details = append(details, *d)
	}
	if d := validateHostField("smtp_host", req.SMTPHost); d != nil {
		details = append(details, *d)
	}
	if d := validatePortField("imap_port", req.IMAPPort); d != nil {
		details = append(details, *d)
	}
	if d := validatePortField("smtp_port", req.SMTPPort); d != nil {
		details = append(details, *d)
	}
	if u := strings.TrimSpace(req.Username); u != "" {
		if d := validateCredentialField("username", u, maxUsernameLen); d != nil {
			details = append(details, *d)
		}
	} else if !useStoredCreds {
		details = append(details, apivalidate.FieldDetail{Field: "username", Message: "required"})
	}

	if req.AuthType == "oauth2" {
		if strings.TrimSpace(req.AccessToken) == "" && !useStoredCreds {
			details = append(details, apivalidate.FieldDetail{Field: "access_token", Message: "required"})
		}
	} else if req.Password != "" {
		if d := validateCredentialField("password", req.Password, maxPasswordLen); d != nil {
			details = append(details, *d)
		}
	} else if !useStoredCreds {
		details = append(details, apivalidate.FieldDetail{Field: "password", Message: "required"})
	}

	if len(details) == 0 {
		return nil
	}
	return apivalidate.NewValidationError(details...)
}

type oauthStartRequest struct {
	Provider   string `json:"provider"`
	Email      string             `json:"email"`
	Name       string             `json:"name"`
	ProviderID string             `json:"provider_id"`
	IMAPHost   string             `json:"imap_host"`
	IMAPPort   int                `json:"imap_port"`
	IMAPTLS    bool               `json:"imap_tls"`
	SMTPHost   string             `json:"smtp_host"`
	SMTPPort   int                `json:"smtp_port"`
	SMTPTLS    bool               `json:"smtp_tls"`
}

func validateOAuthStart(req *oauthStartRequest) *apivalidate.ValidationError {
	var details []apivalidate.FieldDetail
	if req.Provider != string(mailoauth.ProviderGoogle) && req.Provider != string(mailoauth.ProviderMicrosoft) {
		details = append(details, apivalidate.FieldDetail{Field: "provider", Message: "invalid"})
	}
	if d := validateEmailField("email", req.Email); d != nil {
		details = append(details, *d)
	}
	if req.Name != "" && len(req.Name) > maxAccountName {
		details = append(details, apivalidate.FieldDetail{Field: "name", Message: "too long"})
	}
	if d := validateHostField("imap_host", req.IMAPHost); d != nil {
		details = append(details, *d)
	}
	if d := validateHostField("smtp_host", req.SMTPHost); d != nil {
		details = append(details, *d)
	}
	if d := validatePortField("imap_port", req.IMAPPort); d != nil {
		details = append(details, *d)
	}
	if d := validatePortField("smtp_port", req.SMTPPort); d != nil {
		details = append(details, *d)
	}
	if len(details) == 0 {
		return nil
	}
	return apivalidate.NewValidationError(details...)
}

type sendMessageRequest struct {
	AccountID        string   `json:"account_id"`
	To               []string `json:"to"`
	Cc               []string `json:"cc"`
	Bcc              []string `json:"bcc"`
	Subject          string   `json:"subject"`
	BodyText         string   `json:"body_text"`
	BodyHTML         string   `json:"body_html"`
	InReplyTo        string   `json:"in_reply_to"`
	ReplyToMessageID string   `json:"reply_to_message_id"`
	ScheduleAt       *string  `json:"schedule_at"`
	IdempotencyKey   string   `json:"-"`
}

type rescheduleOutboxRequest struct {
	ScheduleAt string `json:"schedule_at"`
}

func validateRescheduleOutbox(req *rescheduleOutboxRequest) (*time.Time, *apivalidate.ValidationError) {
	scheduleAt := strings.TrimSpace(req.ScheduleAt)
	if scheduleAt == "" {
		return nil, apivalidate.NewValidationError(apivalidate.FieldDetail{Field: "schedule_at", Message: "required"})
	}
	parsed, err := time.Parse(time.RFC3339, scheduleAt)
	if err != nil {
		return nil, apivalidate.NewValidationError(apivalidate.FieldDetail{Field: "schedule_at", Message: "invalid"})
	}
	if !parsed.After(time.Now()) {
		return nil, apivalidate.NewValidationError(apivalidate.FieldDetail{Field: "schedule_at", Message: "must be in the future"})
	}
	return &parsed, nil
}

func validateSendMessage(req *sendMessageRequest) *apivalidate.ValidationError {
	var details []apivalidate.FieldDetail
	if strings.TrimSpace(req.AccountID) == "" {
		details = append(details, apivalidate.FieldDetail{Field: "account_id", Message: "required"})
	}
	recipients := append(append([]string{}, req.To...), append(req.Cc, req.Bcc...)...)
	if len(recipients) == 0 {
		details = append(details, apivalidate.FieldDetail{Field: "to", Message: "at least one recipient required"})
	}
	for i, addr := range req.To {
		if d := validateRecipient(addr); d != nil {
			d.Field = "to[" + strconv.Itoa(i) + "]"
			details = append(details, *d)
		}
	}
	for i, addr := range req.Cc {
		if d := validateRecipient(addr); d != nil {
			d.Field = "cc[" + strconv.Itoa(i) + "]"
			details = append(details, *d)
		}
	}
	for i, addr := range req.Bcc {
		if d := validateRecipient(addr); d != nil {
			d.Field = "bcc[" + strconv.Itoa(i) + "]"
			details = append(details, *d)
		}
	}
	if len(req.Subject) > maxSubjectLen {
		details = append(details, apivalidate.FieldDetail{Field: "subject", Message: "too long"})
	}
	if len(req.BodyText) > limits.MaxBodyFieldBytes {
		details = append(details, apivalidate.FieldDetail{Field: "body_text", Message: "too long"})
	}
	if len(req.BodyHTML) > limits.MaxBodyFieldBytes {
		details = append(details, apivalidate.FieldDetail{Field: "body_html", Message: "too long"})
	}
	if req.InReplyTo != "" && len(req.InReplyTo) > 998 {
		details = append(details, apivalidate.FieldDetail{Field: "in_reply_to", Message: "too long"})
	}
	if len(details) == 0 {
		return nil
	}
	return apivalidate.NewValidationError(details...)
}

func validateRecipient(addr string) *apivalidate.FieldDetail {
	addr = strings.TrimSpace(addr)
	if addr == "" {
		return &apivalidate.FieldDetail{Field: "email", Message: "required"}
	}
	if len(addr) > maxEmailLen || containsNewline(addr) {
		return &apivalidate.FieldDetail{Field: "email", Message: "invalid"}
	}
	parsed, err := mail.ParseAddress(addr)
	if err != nil || parsed.Address == "" {
		return &apivalidate.FieldDetail{Field: "email", Message: "invalid"}
	}
	return nil
}

type updateLabelsRequest struct {
	Labels []string `json:"labels"`
}

func validateUpdateLabels(req *updateLabelsRequest) *apivalidate.ValidationError {
	if req.Labels == nil {
		return apivalidate.NewValidationError(apivalidate.FieldDetail{
			Field: "labels", Message: "required",
		})
	}
	for i, label := range req.Labels {
		if containsNewline(label) {
			return apivalidate.NewValidationError(apivalidate.FieldDetail{
				Field: "labels[" + strconv.Itoa(i) + "]", Message: "invalid",
			})
		}
	}
	return nil
}

type updateFlagsRequest struct {
	Flags []string `json:"flags"`
}

func validateUpdateFlags(req *updateFlagsRequest) *apivalidate.ValidationError {
	if req.Flags == nil {
		return apivalidate.NewValidationError(apivalidate.FieldDetail{
			Field: "flags", Message: "required",
		})
	}
	for i, flag := range req.Flags {
		if strings.TrimSpace(flag) == "" || containsNewline(flag) {
			return apivalidate.NewValidationError(apivalidate.FieldDetail{
				Field: "flags[" + strconv.Itoa(i) + "]", Message: "invalid",
			})
		}
	}
	return nil
}

type createRuleRequest struct {
	Name       string `json:"name"`
	AccountID  string `json:"account_id"`
	Priority   int    `json:"priority"`
	RuleKind   string `json:"rule_kind"`
	Conditions any    `json:"conditions"`
	Actions    any    `json:"actions"`
	Workflow   any    `json:"workflow"`
}

func validateCreateRule(req *createRuleRequest) *apivalidate.ValidationError {
	var details []apivalidate.FieldDetail
	if strings.TrimSpace(req.Name) == "" {
		details = append(details, apivalidate.FieldDetail{Field: "name", Message: "required"})
	} else if len(req.Name) > maxRuleName {
		details = append(details, apivalidate.FieldDetail{Field: "name", Message: "too long"})
	}
	hasWorkflow := req.Workflow != nil
	if !hasWorkflow {
		if req.Conditions == nil {
			details = append(details, apivalidate.FieldDetail{Field: "conditions", Message: "required"})
		}
		if req.Actions == nil {
			details = append(details, apivalidate.FieldDetail{Field: "actions", Message: "required"})
		}
	}
	if req.RuleKind != "" && req.RuleKind != "rule" && req.RuleKind != "function" {
		details = append(details, apivalidate.FieldDetail{Field: "rule_kind", Message: "invalid"})
	}
	if len(details) == 0 {
		return nil
	}
	return apivalidate.NewValidationError(details...)
}

type updateRuleRequest struct {
	Name       string `json:"name"`
	Priority   int    `json:"priority"`
	IsActive   bool   `json:"is_active"`
	RuleKind   string `json:"rule_kind"`
	Conditions any    `json:"conditions"`
	Actions    any    `json:"actions"`
	Workflow   any    `json:"workflow"`
}

type simulateRuleSampleMessage struct {
	From           string   `json:"from"`
	To             []string `json:"to"`
	Subject        string   `json:"subject"`
	BodyText       string   `json:"body_text"`
	HasAttachments bool     `json:"has_attachments"`
	Labels         []string `json:"labels,omitempty"`
}

type simulateRuleInlineRule struct {
	Conditions any `json:"conditions"`
	Actions    any `json:"actions"`
	Workflow   any `json:"workflow"`
}

type simulateRuleRequest struct {
	Message *simulateRuleSampleMessage `json:"message"`
	RuleID  string                     `json:"rule_id"`
	Rule    *simulateRuleInlineRule    `json:"rule"`
}

func validateSimulateRule(req *simulateRuleRequest) *apivalidate.ValidationError {
	var details []apivalidate.FieldDetail
	if req.Message == nil {
		details = append(details, apivalidate.FieldDetail{Field: "message", Message: "required"})
	}
	hasRuleID := strings.TrimSpace(req.RuleID) != ""
	hasInlineRule := req.Rule != nil
	if hasRuleID && hasInlineRule {
		details = append(details, apivalidate.FieldDetail{Field: "rule_id", Message: "provide rule_id or rule, not both"})
	}
	if !hasRuleID && !hasInlineRule {
		details = append(details, apivalidate.FieldDetail{Field: "rule_id", Message: "rule_id or rule required"})
	}
	if hasInlineRule {
		if req.Rule.Conditions == nil && req.Rule.Workflow == nil {
			details = append(details, apivalidate.FieldDetail{Field: "rule.conditions", Message: "required"})
		}
		if req.Rule.Actions == nil && req.Rule.Workflow == nil {
			details = append(details, apivalidate.FieldDetail{Field: "rule.actions", Message: "required"})
		}
	}
	if len(details) == 0 {
		return nil
	}
	return apivalidate.NewValidationError(details...)
}

func validateUpdateRule(req *updateRuleRequest) *apivalidate.ValidationError {
	var details []apivalidate.FieldDetail
	if strings.TrimSpace(req.Name) == "" {
		details = append(details, apivalidate.FieldDetail{Field: "name", Message: "required"})
	} else if len(req.Name) > maxRuleName {
		details = append(details, apivalidate.FieldDetail{Field: "name", Message: "too long"})
	}
	if req.Conditions == nil && req.Workflow == nil {
		details = append(details, apivalidate.FieldDetail{Field: "conditions", Message: "required"})
	}
	if req.Actions == nil && req.Workflow == nil {
		details = append(details, apivalidate.FieldDetail{Field: "actions", Message: "required"})
	}
	if req.RuleKind != "" && req.RuleKind != "rule" && req.RuleKind != "function" {
		details = append(details, apivalidate.FieldDetail{Field: "rule_kind", Message: "invalid"})
	}
	if len(details) == 0 {
		return nil
	}
	return apivalidate.NewValidationError(details...)
}

type createWebhookRequest struct {
	Name          string            `json:"name"`
	URL           string            `json:"url"`
	Method        string            `json:"method"`
	Headers       map[string]string `json:"headers"`
	BodyTemplate  string            `json:"body_template"`
	SigningSecret string            `json:"signing_secret"`
	MaxRetries    *int              `json:"max_retries"`
	EventTypes    []string          `json:"event_types"`
	MailScope     *webhookMailScope `json:"mail_scope"`
	DriveScope    *webhookDriveScope `json:"drive_scope"`
	ContactsScope *webhookContactsScope `json:"contacts_scope"`
}

type webhookMailScope struct {
	AllAccounts bool     `json:"all_accounts"`
	AccountIDs  []string `json:"account_ids"`
}

type webhookDriveScope struct {
	AllFolders  bool     `json:"all_folders"`
	FolderPaths []string `json:"folder_paths"`
}

type webhookContactsScope struct {
	AllBooks bool     `json:"all_books"`
	BookIDs  []string `json:"book_ids"`
}

type updateWebhookRequest struct {
	Name          string            `json:"name"`
	URL           string            `json:"url"`
	Method        string            `json:"method"`
	Headers       map[string]string `json:"headers"`
	BodyTemplate  string            `json:"body_template"`
	SigningSecret string            `json:"signing_secret"`
	MaxRetries    *int              `json:"max_retries"`
	EventTypes    []string          `json:"event_types"`
	MailScope     *webhookMailScope `json:"mail_scope"`
	DriveScope    *webhookDriveScope `json:"drive_scope"`
	ContactsScope *webhookContactsScope `json:"contacts_scope"`
}

type previewWebhookMessageRequest struct {
	SenderName    string `json:"sender_name"`
	SenderEmail   string `json:"sender_email"`
	Subject       string `json:"subject"`
	BodyText      string `json:"body_text"`
	BodyHTML      string `json:"body_html"`
	Date          string `json:"date"`
	Recipients    string `json:"recipients"`
	HasAttachment bool   `json:"has_attachment"`
	MessageID     string `json:"message_id"`
}

type previewWebhookRequest struct {
	BodyTemplate string                        `json:"body_template"`
	Message      *previewWebhookMessageRequest `json:"message"`
}

func validateWebhookURL(raw string) *apivalidate.FieldDetail {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return &apivalidate.FieldDetail{Field: "url", Message: "required"}
	}
	if len(raw) > 2048 {
		return &apivalidate.FieldDetail{Field: "url", Message: "too long"}
	}
	if containsNewline(raw) {
		return &apivalidate.FieldDetail{Field: "url", Message: "invalid"}
	}
	u, err := url.Parse(raw)
	if err != nil {
		return &apivalidate.FieldDetail{Field: "url", Message: "invalid"}
	}
	scheme := strings.ToLower(u.Scheme)
	if scheme != "http" && scheme != "https" {
		return &apivalidate.FieldDetail{Field: "url", Message: "invalid scheme"}
	}
	if u.Host == "" {
		return &apivalidate.FieldDetail{Field: "url", Message: "invalid"}
	}
	if u.User != nil {
		return &apivalidate.FieldDetail{Field: "url", Message: "must not contain credentials"}
	}
	return nil
}

func validateWebhookMethod(method string) (string, *apivalidate.FieldDetail) {
	method = strings.TrimSpace(strings.ToUpper(method))
	if method == "" {
		return "", &apivalidate.FieldDetail{Field: "method", Message: "required"}
	}
	if _, ok := allowedWebhookMethods[method]; !ok {
		return "", &apivalidate.FieldDetail{Field: "method", Message: "invalid"}
	}
	return method, nil
}

func isValidHeaderToken(s string) bool {
	if s == "" {
		return false
	}
	for i := 0; i < len(s); i++ {
		c := s[i]
		if c < 0x21 || c == 0x7f || c == '(' || c == ')' || c == '<' || c == '>' ||
			c == '@' || c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' ||
			c == '/' || c == '[' || c == ']' || c == '?' || c == '=' || c == '{' ||
			c == '}' || c == ' ' || c == '\t' {
			return false
		}
	}
	return true
}

func validateWebhookHeaders(headers map[string]string) *apivalidate.ValidationError {
	if len(headers) > maxWebhookHeaders {
		return apivalidate.NewValidationError(apivalidate.FieldDetail{
			Field: "headers", Message: "too many entries",
		})
	}
	for name, value := range headers {
		if len(name) > maxHeaderNameLen {
			return apivalidate.NewValidationError(apivalidate.FieldDetail{
				Field: "headers", Message: "header name too long",
			})
		}
		if len(value) > maxHeaderValueLen {
			return apivalidate.NewValidationError(apivalidate.FieldDetail{
				Field: "headers", Message: "header value too long",
			})
		}
		if containsNewline(name) || containsNewline(value) {
			return apivalidate.NewValidationError(apivalidate.FieldDetail{
				Field: "headers", Message: "invalid header",
			})
		}
		if !isValidHeaderToken(name) {
			return apivalidate.NewValidationError(apivalidate.FieldDetail{
				Field: "headers", Message: "invalid header name",
			})
		}
		if strings.EqualFold(name, "Content-Type") {
			if _, _, err := mime.ParseMediaType(value); err != nil {
				return apivalidate.NewValidationError(apivalidate.FieldDetail{
					Field: "headers", Message: "invalid content-type",
				})
			}
		}
	}
	return nil
}

func validateWebhookBodyTemplate(body string) *apivalidate.FieldDetail {
	if body == "" {
		return nil
	}
	if len(body) > maxWebhookBodyTemplate {
		return &apivalidate.FieldDetail{Field: "body_template", Message: "too large"}
	}
	if !json.Valid([]byte(body)) {
		return &apivalidate.FieldDetail{Field: "body_template", Message: "invalid json"}
	}
	return nil
}

func validateWebhookSigningSecret(secret string) *apivalidate.FieldDetail {
	if secret == "" {
		return nil
	}
	if len(secret) > maxWebhookSecretLen {
		return &apivalidate.FieldDetail{Field: "signing_secret", Message: "too long"}
	}
	if containsNewline(secret) {
		return &apivalidate.FieldDetail{Field: "signing_secret", Message: "invalid"}
	}
	return nil
}

func normalizeWebhookMaxRetries(v *int) (int, *apivalidate.FieldDetail) {
	if v == nil {
		return defaultWebhookRetries, nil
	}
	if *v < 0 || *v > maxWebhookRetries {
		return 0, &apivalidate.FieldDetail{Field: "max_retries", Message: "must be between 0 and 10"}
	}
	return *v, nil
}

func validateCreateWebhook(req *createWebhookRequest) (string, int, *apivalidate.ValidationError) {
	var details []apivalidate.FieldDetail
	if strings.TrimSpace(req.Name) == "" {
		details = append(details, apivalidate.FieldDetail{Field: "name", Message: "required"})
	} else if len(req.Name) > maxWebhookName {
		details = append(details, apivalidate.FieldDetail{Field: "name", Message: "too long"})
	}
	if d := validateWebhookURL(req.URL); d != nil {
		details = append(details, *d)
	}
	method, d := validateWebhookMethod(req.Method)
	if d != nil {
		details = append(details, *d)
	}
	maxRetries, d := normalizeWebhookMaxRetries(req.MaxRetries)
	if d != nil {
		details = append(details, *d)
	}
	if d := validateWebhookSigningSecret(req.SigningSecret); d != nil {
		details = append(details, *d)
	}
	if len(details) > 0 {
		return "", 0, apivalidate.NewValidationError(details...)
	}
	if verr := validateWebhookHeaders(req.Headers); verr != nil {
		return "", 0, verr
	}
	if d := validateWebhookBodyTemplate(req.BodyTemplate); d != nil {
		return "", 0, apivalidate.NewValidationError(*d)
	}
	return method, maxRetries, nil
}

func validateUpdateWebhook(req *updateWebhookRequest) (string, int, *apivalidate.ValidationError) {
	return validateCreateWebhook(&createWebhookRequest{
		Name:          req.Name,
		URL:           req.URL,
		Method:        req.Method,
		Headers:       req.Headers,
		BodyTemplate:  req.BodyTemplate,
		SigningSecret: req.SigningSecret,
		MaxRetries:    req.MaxRetries,
	})
}

func validatePreviewWebhook(req *previewWebhookRequest) *apivalidate.ValidationError {
	var details []apivalidate.FieldDetail
	if req.Message == nil {
		details = append(details, apivalidate.FieldDetail{Field: "message", Message: "required"})
	}
	if strings.TrimSpace(req.BodyTemplate) == "" {
		details = append(details, apivalidate.FieldDetail{Field: "body_template", Message: "required"})
	} else if d := validateWebhookBodyTemplate(req.BodyTemplate); d != nil {
		details = append(details, *d)
	}
	if len(details) == 0 {
		return nil
	}
	return apivalidate.NewValidationError(details...)
}
