package webhooks

import (
	"crypto/subtle"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"regexp"
	"strings"
	"time"

	"source.offmarket.win/aleagle/eaglecast/models"
)

var reSMTPStatus = regexp.MustCompile(`\b([245]\.\d\.\d)\b`)

type azureEvent struct {
	EventType string          `json:"eventType"`
	Data      azureEventData  `json:"data"`
	RawData   json.RawMessage `json:"-"`
}

type azureEventData struct {
	Recipient                string                     `json:"recipient"`
	MessageID                string                     `json:"messageId"`
	InternetMessageID        string                     `json:"internetMessageId"`
	Status                   string                     `json:"status"`
	DeliveryStatusDetails    azureDeliveryStatus        `json:"deliveryStatusDetails"`
	DeliveryAttemptTimestamp *time.Time                 `json:"deliveryAttemptTimestamp"`
	DeliveryAttemptTimeStamp *time.Time                 `json:"deliveryAttemptTimeStamp"`
	AdditionalData           map[string]json.RawMessage `json:"-"`
}

type azureDeliveryStatus struct {
	StatusMessage string `json:"statusMessage"`
}

// Azure handles webhook notifications from Azure Communication Services through Event Grid.
type Azure struct {
	sharedSecret       string
	sharedSecretHeader string
}

// NewAzure returns a new Azure webhook handler.
func NewAzure(sharedSecret, sharedSecretHeader string) *Azure {
	return &Azure{
		sharedSecret:       strings.TrimSpace(sharedSecret),
		sharedSecretHeader: strings.TrimSpace(sharedSecretHeader),
	}
}

// ProcessSubscription processes Event Grid subscription validation requests and
// returns the response payload that should be written to HTTP response body.
func (a *Azure) ProcessSubscription(b []byte) (json.RawMessage, error) {
	events, err := parseAzureEvents(b)
	if err != nil {
		return nil, err
	}
	if len(events) == 0 {
		return nil, errors.New("empty event payload")
	}

	// Validation code arrives in the first event for subscription validation flow.
	var payload map[string]json.RawMessage
	if err := json.Unmarshal(events[0].RawData, &payload); err != nil {
		return nil, fmt.Errorf("error reading validation payload: %v", err)
	}

	rawData, ok := payload["data"]
	if !ok {
		return nil, errors.New("missing event data")
	}

	var data map[string]string
	if err := json.Unmarshal(rawData, &data); err != nil {
		return nil, fmt.Errorf("error reading validation data: %v", err)
	}

	code := strings.TrimSpace(data["validationCode"])
	if code == "" {
		return nil, errors.New("missing validationCode in subscription payload")
	}

	res, _ := json.Marshal(map[string]string{
		"validationResponse": code,
	})
	return json.RawMessage(res), nil
}

// ProcessBounce parses Azure Event Grid email delivery events and returns bounce entries.
func (a *Azure) ProcessBounce(req *http.Request, b []byte) ([]models.Bounce, error) {
	if err := a.verifyAuth(req); err != nil {
		return nil, err
	}

	events, err := parseAzureEvents(b)
	if err != nil {
		return nil, err
	}

	out := make([]models.Bounce, 0, len(events))
	for _, ev := range events {
		// Ignore non-delivery report events.
		if ev.EventType != "Microsoft.Communication.EmailDeliveryReportReceived" {
			continue
		}

		typ, ok := mapAzureStatus(ev.Data.Status, ev.Data.DeliveryStatusDetails.StatusMessage)
		if !ok {
			continue
		}

		email := strings.ToLower(strings.TrimSpace(ev.Data.Recipient))
		if email == "" {
			continue
		}

		tstamp := ev.Data.DeliveryAttemptTimestamp
		if tstamp == nil {
			tstamp = ev.Data.DeliveryAttemptTimeStamp
		}

		createdAt := time.Now()
		if tstamp != nil && !tstamp.IsZero() {
			createdAt = *tstamp
		}

		out = append(out, models.Bounce{
			Email:     email,
			Type:      typ,
			Meta:      json.RawMessage(ev.RawData),
			Source:    "azure",
			CreatedAt: createdAt,
		})
	}

	return out, nil
}

func (a *Azure) verifyAuth(req *http.Request) error {
	const (
		defaultSecretHeader = "X-EagleCast-Webhook-Secret"
		querySecretParam    = "code"
	)

	// If no local credential is configured, allow webhook payloads.
	if a.sharedSecret == "" {
		return nil
	}

	if req == nil {
		return errors.New("missing azure event grid request context")
	}

	querySecret := strings.TrimSpace(req.URL.Query().Get(querySecretParam))
	if secretsEqual(a.sharedSecret, querySecret) {
		return nil
	}

	headerName := a.sharedSecretHeader
	if headerName == "" {
		headerName = defaultSecretHeader
	}
	headerSecret := strings.TrimSpace(req.Header.Get(headerName))
	if secretsEqual(a.sharedSecret, headerSecret) {
		return nil
	}

	return errors.New("invalid azure event grid shared secret")
}

func secretsEqual(expected, given string) bool {
	if expected == "" || given == "" {
		return false
	}
	return subtle.ConstantTimeCompare([]byte(expected), []byte(given)) == 1
}

func parseAzureEvents(b []byte) ([]azureEvent, error) {
	var raws []json.RawMessage
	if err := json.Unmarshal(b, &raws); err != nil {
		return nil, fmt.Errorf("error unmarshalling azure notification array: %v", err)
	}

	events := make([]azureEvent, 0, len(raws))
	for _, raw := range raws {
		ev := azureEvent{RawData: raw}
		if err := json.Unmarshal(raw, &ev); err != nil {
			return nil, fmt.Errorf("error unmarshalling azure event: %v", err)
		}
		events = append(events, ev)
	}

	return events, nil
}

func mapAzureStatus(status, details string) (string, bool) {
	s := strings.ToLower(strings.TrimSpace(status))
	d := strings.ToLower(strings.TrimSpace(details))

	switch s {
	case "bounced", "suppressed":
		return models.BounceTypeHard, true
	case "failed":
		// Infer severity from SMTP-enhanced status if available.
		if m := reSMTPStatus.FindStringSubmatch(d); len(m) > 1 {
			if strings.HasPrefix(m[1], "5.") {
				return models.BounceTypeHard, true
			}
			if strings.HasPrefix(m[1], "4.") {
				return models.BounceTypeSoft, true
			}
		}

		if strings.Contains(d, "mailbox full") ||
			strings.Contains(d, "temporar") ||
			strings.Contains(d, "timeout") ||
			strings.Contains(d, "rate limit") ||
			strings.Contains(d, "throttle") {
			return models.BounceTypeSoft, true
		}
		if strings.Contains(d, "invalid") ||
			strings.Contains(d, "does not exist") ||
			strings.Contains(d, "unknown user") ||
			strings.Contains(d, "domain") {
			return models.BounceTypeHard, true
		}

		return models.BounceTypeSoft, true
	case "filteredspam", "quarantined":
		if strings.Contains(d, "spam complaint") ||
			strings.Contains(d, "complaint") ||
			strings.Contains(d, "abuse") {
			return models.BounceTypeComplaint, true
		}
		return models.BounceTypeSoft, true
	default:
		return "", false
	}
}
