package orchestrator

import (
	"context"
	"strings"

	paymodel "github.com/tech/sendico/payments/orchestrator/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	cons "github.com/tech/sendico/pkg/messaging/consumer"
	paymentgateway "github.com/tech/sendico/pkg/messaging/notifications/paymentgateway"
	np "github.com/tech/sendico/pkg/messaging/notifications/processor"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mservice"
	"go.uber.org/zap"
)

func (s *Service) startGatewayConsumers() {
	if s == nil || s.gatewayBroker == nil {
		return
	}
	processor := paymentgateway.NewPaymentGatewayExecutionProcessor(s.logger, s.onGatewayExecution)
	s.consumeGatewayProcessor(processor)
}

func (s *Service) consumeGatewayProcessor(processor np.EnvelopeProcessor) {
	consumer, err := cons.NewConsumer(s.logger, s.gatewayBroker, processor.GetSubject())
	if err != nil {
		s.logger.Warn("Failed to create payment gateway consumer", zap.Error(err), zap.String("event", processor.GetSubject().ToString()))
		return
	}
	s.gatewayConsumers = append(s.gatewayConsumers, consumer)
	go func() {
		if err := consumer.ConsumeMessages(processor.Process); err != nil {
			s.logger.Warn("Payment gateway consumer stopped", zap.Error(err), zap.String("event", processor.GetSubject().ToString()))
		}
	}()
}

func (s *Service) onGatewayExecution(ctx context.Context, exec *model.PaymentGatewayExecution) error {
	if exec == nil {
		return merrors.InvalidArgument("payment gateway execution is nil", "execution")
	}
	paymentRef := strings.TrimSpace(exec.PaymentIntentID)
	if paymentRef == "" {
		return merrors.InvalidArgument("payment_intent_id is required", "payment_intent_id")
	}
	if s.storage == nil || s.storage.Payments() == nil {
		return errStorageUnavailable
	}
	store := s.storage.Payments()
	payment, err := store.GetByPaymentRef(ctx, paymentRef)
	if err != nil {
		return err
	}
	if payment.Metadata == nil {
		payment.Metadata = map[string]string{}
	}
	if exec.RequestID != "" {
		payment.Metadata["gateway_request_id"] = exec.RequestID
	}
	if exec.QuoteRef != "" {
		payment.Metadata["gateway_quote_ref"] = exec.QuoteRef
	}
	if exec.ExecutedMoney != nil {
		payment.Metadata["gateway_executed_amount"] = exec.ExecutedMoney.Amount
		payment.Metadata["gateway_executed_currency"] = exec.ExecutedMoney.Currency
	}
	payment.Metadata["gateway_confirmation_status"] = string(exec.Status)

	updatedPlan := updateExecutionStepsFromGatewayExecution(payment, exec)
	switch exec.Status {
	case model.ConfirmationStatusConfirmed, model.ConfirmationStatusClarified:
		if payment.PaymentPlan != nil && updatedPlan && payment.ExecutionPlan != nil && !executionPlanComplete(payment.ExecutionPlan) {
			return s.resumePaymentPlan(ctx, store, payment)
		}
		payment.State = paymodel.PaymentStateSettled
		payment.FailureCode = paymodel.PaymentFailureCodeUnspecified
		payment.FailureReason = ""
	case model.ConfirmationStatusRejected:
		payment.State = paymodel.PaymentStateFailed
		payment.FailureCode = paymodel.PaymentFailureCodePolicy
		payment.FailureReason = "gateway_rejected"
	case model.ConfirmationStatusTimeout:
		payment.State = paymodel.PaymentStateFailed
		payment.FailureCode = paymodel.PaymentFailureCodePolicy
		payment.FailureReason = "confirmation_timeout"
	default:
		s.logger.Warn("Unhandled gateway confirmation status", zap.String("status", string(exec.Status)), zap.String("payment_ref", paymentRef))
	}
	if err := store.Update(ctx, payment); err != nil {
		return err
	}
	s.logger.Info("Payment gateway execution applied", zap.String("payment_ref", paymentRef), zap.String("status", string(exec.Status)), zap.String("service", string(mservice.PaymentGateway)))
	return nil
}

func updateExecutionStepsFromGatewayExecution(payment *paymodel.Payment, exec *model.PaymentGatewayExecution) bool {
	if payment == nil || exec == nil || payment.PaymentPlan == nil {
		return false
	}
	requestID := strings.TrimSpace(exec.RequestID)
	if requestID == "" {
		return false
	}
	execPlan := ensureExecutionPlanForPlan(payment, payment.PaymentPlan)
	if execPlan == nil {
		return false
	}
	status := executionStepStatusFromGatewayStatus(exec.Status)
	if status == "" {
		return false
	}
	updated := false
	for idx, planStep := range payment.PaymentPlan.Steps {
		if planStep == nil {
			continue
		}
		if idx >= len(execPlan.Steps) {
			continue
		}
		execStep := execPlan.Steps[idx]
		if execStep == nil {
			execStep = &paymodel.ExecutionStep{Code: planStepID(planStep, idx), Description: describePlanStep(planStep)}
			execPlan.Steps[idx] = execStep
		}
		if strings.EqualFold(strings.TrimSpace(execStep.TransferRef), requestID) {
			setExecutionStepStatus(execStep, status)
			updated = true
			continue
		}
		if execStep.TransferRef == "" && planStep.Rail == paymodel.RailProviderSettlement {
			if planStep.Action == paymodel.RailOperationObserveConfirm || planStep.Action == paymodel.RailOperationSend {
				execStep.TransferRef = requestID
				setExecutionStepStatus(execStep, status)
				updated = true
			}
		}
	}
	return updated
}

func executionStepStatusFromGatewayStatus(status model.ConfirmationStatus) string {
	switch status {
	case model.ConfirmationStatusConfirmed, model.ConfirmationStatusClarified:
		return executionStepStatusConfirmed
	case model.ConfirmationStatusRejected, model.ConfirmationStatusTimeout:
		return executionStepStatusFailed
	default:
		return ""
	}
}

func (s *Service) Shutdown() {
	if s == nil {
		return
	}
	for _, consumer := range s.gatewayConsumers {
		if consumer != nil {
			consumer.Close()
		}
	}
}
