package gateway

import (
	"context"
	"fmt"

	"github.com/tech/sendico/gateway/tron/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/payments/rail"
	"go.uber.org/zap"
)

func isFinalStatus(t *model.Transfer) bool {
	if t == nil {
		return false
	}
	switch t.Status {
	case model.TransferStatusFailed, model.TransferStatusSuccess, model.TransferStatusCancelled:
		return true
	default:
		return false
	}
}

func isFinalTransferStatus(status model.TransferStatus) bool {
	switch status {
	case model.TransferStatusFailed, model.TransferStatusSuccess, model.TransferStatusCancelled:
		return true
	default:
		return false
	}
}

func toOpStatus(t *model.Transfer) (rail.OperationResult, error) {
	switch t.Status {
	case model.TransferStatusFailed:
		return rail.OperationResultFailed, nil
	case model.TransferStatusSuccess:
		return rail.OperationResultSuccess, nil
	case model.TransferStatusCancelled:
		return rail.OperationResultCancelled, nil
	default:
		return rail.OperationResultFailed, merrors.InvalidArgument(fmt.Sprintf("unexpected transfer status: %s", t.Status), "transfer.status")
	}
}

func toError(t *model.Transfer) string {
	if t == nil {
		return ""
	}
	if t.Status == model.TransferStatusSuccess {
		return ""
	}
	return t.FailureReason
}

func (s *Service) updateTransferStatus(ctx context.Context, transferRef string, status model.TransferStatus, failureReason, txHash string) error {
	if !isFinalTransferStatus(status) {
		_, err := s.storage.Transfers().UpdateStatus(ctx, transferRef, status, failureReason, txHash)
		if err != nil {
			s.logger.Warn("Failed to update transfer status", zap.String("transfer_ref", transferRef), zap.String("status", string(status)), zap.Error(err))
		}
		return err
	}

	_, err := s.executeTransaction(ctx, func(txCtx context.Context) (any, error) {
		transfer, statusErr := s.storage.Transfers().UpdateStatus(txCtx, transferRef, status, failureReason, txHash)
		if statusErr != nil {
			return nil, statusErr
		}
		if isFinalStatus(transfer) {
			if emitErr := s.emitTransferStatusEvent(txCtx, transfer); emitErr != nil {
				return nil, emitErr
			}
		}
		return transfer, nil
	})
	if err != nil {
		s.logger.Warn("Failed to update transfer status", zap.String("transfer_ref", transferRef), zap.String("status", string(status)), zap.Error(err))
		return err
	}
	return nil
}

func (s *Service) emitTransferStatusEvent(ctx context.Context, transfer *model.Transfer) error {
	if s == nil || transfer == nil {
		return nil
	}
	if s.producer == nil || s.outboxStore() == nil {
		return nil
	}

	status, err := toOpStatus(transfer)
	if err != nil {
		s.logger.Warn("Failed to map transfer status for transfer status event", zap.Error(err), zap.String("transfer_ref", transfer.TransferRef))
		return err
	}

	exec := pmodel.PaymentGatewayExecution{
		PaymentIntentID: transfer.IntentRef,
		IdempotencyKey:  transfer.IdempotencyKey,
		ExecutedMoney:   transfer.NetAmount,
		PaymentRef:      transfer.PaymentRef,
		Status:          status,
		OperationRef:    transfer.OperationRef,
		Error:           toError(transfer),
		TransferRef:     transfer.TransferRef,
	}
	env := paymentgateway.PaymentGatewayExecution(mservice.ChainGateway, &exec)
	if err := s.sendWithOutbox(ctx, env); err != nil {
		s.logger.Warn("Failed to publish transfer status event", zap.Error(err), zap.String("transfer_ref", transfer.TransferRef))
		return err
	}
	return nil
}
