package sfcrmappservice

import (
	"context"
	"strconv"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/queryv1"
)

const (
	ProductNotificationPath         = "/product/notifications"
	productNotificationDefaultLimit = 50
	productNotificationMaxLimit     = 200
	connectNotificationLimit        = 50
	notificationObjectID            = "Notification"
	notificationFieldID             = "Id"
	notificationFieldRecipientID    = "recipient_id"
	notificationFieldTitle          = "title"
	notificationFieldBody           = "body"
	notificationFieldLink           = "link"
	notificationFieldIsRead         = "is_read"
	notificationFieldType           = "notification_type"
	notificationFieldCreatedDate    = "CreatedDate"
)

var supportedConnectNotificationPaths = map[string]string{
	"/services/data/v59.0/connect/notifications": "v59.0",
	"/services/data/v60.0/connect/notifications": "v60.0",
	"/services/data/v61.0/connect/notifications": "v61.0",
	"/services/data/v62.0/connect/notifications": "v62.0",
	"/services/data/v63.0/connect/notifications": "v63.0",
}

type notificationProjection struct {
	ID           string `json:"id"`
	RecipientID  string `json:"recipientId"`
	Type         string `json:"type"`
	Unread       bool   `json:"unread"`
	CreatedDate  string `json:"createdDate"`
	MessageBody  string `json:"messageBody"`
	MessageTitle string `json:"messageTitle"`
}

type notificationResult struct {
	notificationProjection
	URL any
}

type productNotificationProjection struct {
	notificationProjection
	URL string `json:"url,omitempty"`
}

type connectNotificationProjection struct {
	notificationProjection
	URL any `json:"url"`
}

func notificationRequest(unreadOnly bool, limit uint32) semantic.Request {
	recipient := semantic.Predicate{
		Kind: semantic.PredicateComparison, Operator: semantic.ComparisonEqual,
		Left:  semantic.Operand{Kind: semantic.OperandField, Field: notificationFieldRecipientID},
		Right: semantic.Operand{Kind: semantic.OperandTrustedContext, TrustedContext: semantic.TrustedContextPrincipalID},
	}
	filter := recipient
	if unreadOnly {
		unread := false
		filter = semantic.Predicate{Kind: semantic.PredicateAnd, Children: []semantic.Predicate{
			recipient,
			{Kind: semantic.PredicateFieldComparison, Field: notificationFieldIsRead, Operator: semantic.ComparisonEqual, Value: &semantic.Scalar{Boolean: &unread}},
		}}
	}
	fields := []string{
		notificationFieldID, notificationFieldRecipientID, notificationFieldTitle, notificationFieldBody,
		notificationFieldLink, notificationFieldIsRead, notificationFieldType, notificationFieldCreatedDate,
	}
	selections := make([]semantic.Selection, len(fields))
	for index, field := range fields {
		selections[index] = semantic.Selection{Kind: semantic.SelectionField, Field: field}
	}
	return semantic.Request{
		ObjectID: notificationObjectID, Selections: selections, Filter: filter, Limit: limit,
		OrderBy: []semantic.Sort{{Field: notificationFieldCreatedDate, Direction: semantic.SortDescending}},
	}
}

func queryNotifications(ctx context.Context, runtime RuntimeQueryFunc, bearer string, unreadOnly bool, limit uint32) ([]notificationResult, int, error) {
	request := notificationRequest(unreadOnly, limit)
	if err := semantic.ValidateRequest(ctx, request); err != nil {
		return nil, 0, err
	}
	response, err := runtime(ctx, bearer, request)
	if err != nil {
		return nil, 0, err
	}
	if err := semantic.ValidateResponse(response); err != nil {
		return nil, 0, err
	}
	result := make([]notificationResult, 0, len(response.Records))
	unreadCount := 0
	for _, record := range response.Records {
		mapped, err := queryRecordFromSemantic(record)
		if err != nil {
			return nil, 0, err
		}
		isRead := notificationBool(mapped.Fields[notificationFieldIsRead])
		if !isRead {
			unreadCount++
		}
		result = append(result, notificationResult{notificationProjection: notificationProjection{
			ID: notificationString(mapped.Fields[notificationFieldID]), RecipientID: notificationString(mapped.Fields[notificationFieldRecipientID]),
			Type: notificationString(mapped.Fields[notificationFieldType]), Unread: !isRead,
			CreatedDate: notificationString(mapped.Fields[notificationFieldCreatedDate]), MessageBody: notificationString(mapped.Fields[notificationFieldBody]),
			MessageTitle: notificationString(mapped.Fields[notificationFieldTitle]),
		}, URL: mapped.Fields[notificationFieldLink],
		})
	}
	return result, unreadCount, nil
}

func productNotificationProjections(records []notificationResult) []productNotificationProjection {
	result := make([]productNotificationProjection, len(records))
	for index, record := range records {
		result[index] = productNotificationProjection{
			notificationProjection: record.notificationProjection,
			URL:                    notificationString(record.URL),
		}
	}
	return result
}

func connectNotificationProjections(records []notificationResult) []connectNotificationProjection {
	result := make([]connectNotificationProjection, len(records))
	for index, record := range records {
		result[index] = connectNotificationProjection{
			notificationProjection: record.notificationProjection,
			URL:                    record.URL,
		}
	}
	return result
}

func notificationLimit(raw string) (uint32, error) {
	if raw == "" {
		return productNotificationDefaultLimit, nil
	}
	value, err := strconv.Atoi(raw)
	if err != nil || value <= 0 {
		return 0, strconv.ErrSyntax
	}
	if value > productNotificationMaxLimit {
		value = productNotificationMaxLimit
	}
	return uint32(value), nil
}

func notificationString(value any) string {
	result, _ := value.(string)
	return result
}

func notificationBool(value any) bool {
	result, _ := value.(bool)
	return result
}
