package psvc

import (
	"context"
	"strconv"
	"strings"
	"time"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/agg"
	msg "github.com/tech/sendico/pkg/messaging"
	pon "github.com/tech/sendico/pkg/messaging/notifications/paymentorchestrator"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mutil/mzap"
	"go.uber.org/zap"
)

const (
	paymentStatusEventSender = "payments.orchestrator.v2"
)

type paymentStatusPublisher interface {
	Publish(ctx context.Context, in paymentStatusPublishInput) error
}

type paymentStatusPublishInput struct {
	Payment       *agg.Payment
	PreviousState agg.State
	CurrentState  agg.State
	OccurredAt    time.Time
	Event         string
}

type noopPaymentStatusPublisher struct{}

func (noopPaymentStatusPublisher) Publish(_ context.Context, _ paymentStatusPublishInput) error {
	return nil
}

type brokerPaymentStatusPublisher struct {
	logger   mlogger.Logger
	producer msg.Producer
}

func newPaymentStatusPublisher(logger mlogger.Logger, producer msg.Producer) paymentStatusPublisher {
	if producer == nil {
		return noopPaymentStatusPublisher{}
	}
	if logger == nil {
		logger = zap.NewNop()
	}
	return &brokerPaymentStatusPublisher{
		logger:   logger.Named("status_publisher"),
		producer: producer,
	}
}

func (p *brokerPaymentStatusPublisher) Publish(_ context.Context, in paymentStatusPublishInput) error {
	if in.Payment == nil {
		return nil
	}
	payment := in.Payment

	paymentRef := strings.TrimSpace(payment.PaymentRef)
	if paymentRef == "" || payment.OrganizationRef.IsZero() {
		p.logger.Warn("Skipping payment status publish due to missing identifiers",
			zap.String("payment_ref", paymentRef),
			mzap.ObjRef("organization_ref", payment.OrganizationRef),
		)
		return nil
	}

	occurredAt := in.OccurredAt.UTC()
	if occurredAt.IsZero() {
		occurredAt = time.Now().UTC()
	}
	eventName := strings.TrimSpace(in.Event)
	if eventName == "" {
		eventName = "state_changed"
	}

	message := &model.PaymentStatusUpdated{
		EventID:     buildPaymentStatusEventID(paymentRef, payment.Version, in.CurrentState),
		Type:        model.PaymentStatusUpdatedType,
		OccurredAt:  occurredAt,
		PublishedAt: time.Now().UTC(),
		Data: model.PaymentStatusUpdatedData{
			OrganizationRef:  payment.OrganizationRef,
			PaymentRef:       paymentRef,
			QuotationRef:     strings.TrimSpace(payment.QuotationRef),
			ClientPaymentRef: strings.TrimSpace(payment.ClientPaymentRef),
			IdempotencyKey:   strings.TrimSpace(payment.IdempotencyKey),
			State:            string(in.CurrentState),
			PreviousState:    normalizePreviousState(in.PreviousState, in.CurrentState),
			Version:          payment.Version,
			IsTerminal:       isTerminalState(in.CurrentState),
			Event:            eventName,
		},
	}

	envelope := pon.PaymentStatusUpdated(paymentStatusEventSender, message)

	if err := p.producer.SendMessage(envelope); err != nil {
		return err
	}
	return nil
}

func normalizePreviousState(previous, current agg.State) string {
	if previous == current || previous == agg.StateUnspecified {
		return ""
	}
	return string(previous)
}

func isTerminalState(state agg.State) bool {
	switch state {
	case agg.StateSettled, agg.StateNeedsAttention, agg.StateFailed:
		return true
	default:
		return false
	}
}

func buildPaymentStatusEventID(paymentRef string, version uint64, state agg.State) string {
	return paymentRef + ":" + strconv.FormatUint(version, 10) + ":" + string(state)
}
