package gateway

import (
	"context"
	"fmt"

	"github.com/tech/sendico/gateway/aurora/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"
	paytypes "github.com/tech/sendico/pkg/payments/types"
	"go.uber.org/zap"
)

func isFinalStatus(t *model.CardPayout) bool {
	if t == nil {
		return false
	}
	return isFinalPayoutStatus(t.Status)
}

func isFinalPayoutStatus(status model.PayoutStatus) bool {
	switch status {
	case model.PayoutStatusFailed, model.PayoutStatusSuccess, model.PayoutStatusCancelled:
		return true
	default:
		return false
	}
}

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

func (p *cardPayoutProcessor) updatePayoutStatus(ctx context.Context, state *model.CardPayout) error {
	if !isFinalStatus(state) {
		if err := p.store.Payouts().Upsert(ctx, state); err != nil {
			p.logger.Warn("Failed to update transfer status", zap.Error(err), mzap.ObjRef("payout_ref", state.ID),
				zap.String("payment_ref", state.PaymentRef), zap.String("status", string(state.Status)),
			)
			return err
		}
		p.observeExecutionState(state)
		return nil
	}

	_, err := p.executeTransaction(ctx, func(txCtx context.Context) (any, error) {
		if upsertErr := p.store.Payouts().Upsert(txCtx, state); upsertErr != nil {
			return nil, upsertErr
		}
		if isFinalStatus(state) {
			if emitErr := p.emitTransferStatusEvent(txCtx, state); emitErr != nil {
				return nil, emitErr
			}
		}
		return struct{}{}, nil
	})
	if err != nil {
		p.logger.Warn("Failed to update transfer status", zap.Error(err), mzap.ObjRef("payout_ref", state.ID),
			zap.String("payment_ref", state.PaymentRef), zap.String("status", string(state.Status)),
		)
		return err
	}
	p.observeExecutionState(state)
	return nil
}

func (p *cardPayoutProcessor) emitTransferStatusEvent(ctx context.Context, payout *model.CardPayout) error {
	if p == nil || payout == nil {
		return nil
	}
	if p.producer == nil || p.outboxStore() == nil {
		return nil
	}

	status, err := toOpStatus(payout)
	if err != nil {
		p.logger.Warn("Failed to convert payout status to operation status for transfer status event", zap.Error(err),
			mzap.ObjRef("payout_ref", payout.ID), zap.String("payment_ref", payout.PaymentRef), zap.String("status", string(payout.Status)))
		return err
	}

	exec := pmodel.PaymentGatewayExecution{
		PaymentIntentID: payout.IntentRef,
		IdempotencyKey:  payout.IdempotencyKey,
		ExecutedMoney: &paytypes.Money{
			Amount:   fmt.Sprintf("%d", payout.AmountMinor),
			Currency: payout.Currency,
		},
		PaymentRef:   payout.PaymentRef,
		Status:       status,
		OperationRef: payout.OperationRef,
		Error:        payout.FailureReason,
		TransferRef:  payout.GetID().Hex(),
	}
	env := paymentgateway.PaymentGatewayExecution(mservice.MntxGateway, &exec)
	if err := p.sendWithOutbox(ctx, env); err != nil {
		p.logger.Warn("Failed to publish transfer status event", zap.Error(err), mzap.ObjRef("transfer_ref", payout.ID))
		return err
	}
	return nil
}
