package gateway

import (
	"context"

	"github.com/tech/sendico/gateway/chsettle/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	paymentgateway "github.com/tech/sendico/pkg/messaging/notifications/paymentgateway"
	pmodel "github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mservice"
	"github.com/tech/sendico/pkg/mutil/mzap"
	"github.com/tech/sendico/pkg/payments/rail"
	"go.uber.org/zap"
)

func isFinalStatus(t *model.PaymentRecord) bool {
	switch t.Status {
	case model.PaymentStatusFailed, model.PaymentStatusSuccess, model.PaymentStatusCancelled:
		return true
	default:
		return false
	}
}

func toOpStatus(t *model.PaymentRecord) (rail.OperationResult, error) {
	switch t.Status {
	case model.PaymentStatusFailed:
		return rail.OperationResultFailed, nil
	case model.PaymentStatusSuccess:
		return rail.OperationResultSuccess, nil
	case model.PaymentStatusCancelled:
		return rail.OperationResultCancelled, nil
	default:
		return rail.OperationResultFailed, merrors.InvalidArgument("unexpected transfer status", "payment.status")
	}
}

func (s *Service) updateTransferStatus(ctx context.Context, record *model.PaymentRecord) error {
	if record == nil {
		return merrors.InvalidArgument("payment record is required", "record")
	}
	s.logger.Debug("Persisting transfer status",
		zap.String("idempotency_key", record.IdempotencyKey),
		zap.String("payment_ref", record.PaymentIntentID),
		zap.String("status", string(record.Status)),
		zap.Bool("is_final", isFinalStatus(record)))
	if !isFinalStatus(record) {
		if err := s.repo.Payments().Upsert(ctx, record); err != nil {
			s.logger.Warn("Failed to update transfer status", zap.String("payment_ref", record.PaymentIntentID), zap.String("status", string(record.Status)), zap.Error(err))
			return err
		}
		s.logger.Debug("Transfer status persisted (non-final)",
			zap.String("idempotency_key", record.IdempotencyKey),
			zap.String("status", string(record.Status)))
		return nil
	}

	_, err := s.executeTransaction(ctx, func(txCtx context.Context) (any, error) {
		if upsertErr := s.repo.Payments().Upsert(txCtx, record); upsertErr != nil {
			return nil, upsertErr
		}
		if isFinalStatus(record) {
			if emitErr := s.emitTransferStatusEvent(txCtx, record); emitErr != nil {
				return nil, emitErr
			}
		}
		return struct{}{}, nil
	})
	if err != nil {
		s.logger.Warn("Failed to update transfer status", zap.String("payment_ref", record.PaymentIntentID), zap.String("status", string(record.Status)), zap.Error(err))
		return err
	}
	s.logger.Info("Transfer status persisted (final)",
		zap.String("idempotency_key", record.IdempotencyKey),
		zap.String("status", string(record.Status)))
	return nil
}

func (s *Service) emitTransferStatusEvent(ctx context.Context, record *model.PaymentRecord) error {
	if s == nil || record == nil {
		return nil
	}
	if s.producer == nil || s.outboxStore() == nil {
		return nil
	}
	status, err := toOpStatus(record)
	if err != nil {
		s.logger.Warn("Failed to map transfer status for transfer status event", zap.Error(err), mzap.ObjRef("transfer_ref", record.ID))
		return err
	}

	exec := pmodel.PaymentGatewayExecution{
		PaymentIntentID: record.PaymentIntentID,
		IdempotencyKey:  record.IdempotencyKey,
		ExecutedMoney:   record.ExecutedMoney,
		PaymentRef:      record.PaymentRef,
		Status:          status,
		OperationRef:    record.OperationRef,
		Error:           record.FailureReason,
		TransferRef:     record.ID.Hex(),
	}
	env := paymentgateway.PaymentGatewayExecution(mservice.ChSettle, &exec)
	if sendErr := s.sendWithOutbox(ctx, env); sendErr != nil {
		s.logger.Warn("Failed to publish transfer status event", zap.Error(sendErr), mzap.ObjRef("transfer_ref", record.ID))
		return sendErr
	}
	return nil
}
