package orchestrator

import (
	"context"
	"strings"

	chainclient "github.com/tech/sendico/gateway/chain/client"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/payments/rail"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
)

type providerSettlementGateway struct {
	client       chainclient.Client
	rail         string
	network      string
	capabilities rail.RailCapabilities
}

func NewProviderSettlementGateway(client chainclient.Client, cfg chainclient.RailGatewayConfig) rail.RailGateway {
	railName := strings.ToUpper(strings.TrimSpace(cfg.Rail))
	if railName == "" {
		railName = "PROVIDER_SETTLEMENT"
	}
	return &providerSettlementGateway{
		client:       client,
		rail:         railName,
		network:      strings.ToUpper(strings.TrimSpace(cfg.Network)),
		capabilities: cfg.Capabilities,
	}
}

func (g *providerSettlementGateway) Rail() string {
	return g.rail
}

func (g *providerSettlementGateway) Network() string {
	return g.network
}

func (g *providerSettlementGateway) Capabilities() rail.RailCapabilities {
	return g.capabilities
}

func (g *providerSettlementGateway) Send(ctx context.Context, req rail.TransferRequest) (rail.RailResult, error) {
	if g.client == nil {
		return rail.RailResult{}, merrors.Internal("provider settlement gateway: client is required")
	}
	idempotencyKey := strings.TrimSpace(req.IdempotencyKey)
	if idempotencyKey == "" {
		return rail.RailResult{}, merrors.InvalidArgument("provider settlement gateway: idempotency_key is required")
	}
	currency := strings.TrimSpace(req.Currency)
	amount := strings.TrimSpace(req.Amount)
	if currency == "" || amount == "" {
		return rail.RailResult{}, merrors.InvalidArgument("provider settlement gateway: amount is required")
	}
	metadata := cloneMetadata(req.Metadata)
	if metadata == nil {
		metadata = map[string]string{}
	}
	if strings.TrimSpace(metadata[providerSettlementMetaPaymentIntentID]) == "" {
		if ref := strings.TrimSpace(req.ClientReference); ref != "" {
			metadata[providerSettlementMetaPaymentIntentID] = ref
		}
	}
	if strings.TrimSpace(metadata[providerSettlementMetaPaymentIntentID]) == "" {
		return rail.RailResult{}, merrors.InvalidArgument("provider settlement gateway: payment_intent_id is required")
	}
	if strings.TrimSpace(metadata[providerSettlementMetaOutgoingLeg]) == "" && g.rail != "" {
		metadata[providerSettlementMetaOutgoingLeg] = strings.ToLower(strings.TrimSpace(g.rail))
	}
	submitReq := &chainv1.SubmitTransferRequest{
		IdempotencyKey:  idempotencyKey,
		OrganizationRef: strings.TrimSpace(req.OrganizationRef),
		SourceWalletRef: strings.TrimSpace(req.FromAccountID),
		Amount: &moneyv1.Money{
			Currency: currency,
			Amount:   amount,
		},
		Metadata:        metadata,
		ClientReference: strings.TrimSpace(req.ClientReference),
	}
	if dest := buildProviderSettlementDestination(req); dest != nil {
		submitReq.Destination = dest
	}
	resp, err := g.client.SubmitTransfer(ctx, submitReq)
	if err != nil {
		return rail.RailResult{}, err
	}
	if resp == nil || resp.GetTransfer() == nil {
		return rail.RailResult{}, merrors.Internal("provider settlement gateway: missing transfer response")
	}
	transfer := resp.GetTransfer()
	return rail.RailResult{
		ReferenceID: strings.TrimSpace(transfer.GetTransferRef()),
		Status:      providerSettlementStatusFromTransfer(transfer.GetStatus()),
		FinalAmount: railMoneyFromProto(transfer.GetNetAmount()),
	}, nil
}

func (g *providerSettlementGateway) Observe(ctx context.Context, referenceID string) (rail.ObserveResult, error) {
	if g.client == nil {
		return rail.ObserveResult{}, merrors.Internal("provider settlement gateway: client is required")
	}
	ref := strings.TrimSpace(referenceID)
	if ref == "" {
		return rail.ObserveResult{}, merrors.InvalidArgument("provider settlement gateway: reference_id is required")
	}
	resp, err := g.client.GetTransfer(ctx, &chainv1.GetTransferRequest{TransferRef: ref})
	if err != nil {
		return rail.ObserveResult{}, err
	}
	if resp == nil || resp.GetTransfer() == nil {
		return rail.ObserveResult{}, merrors.Internal("provider settlement gateway: missing transfer response")
	}
	transfer := resp.GetTransfer()
	return rail.ObserveResult{
		ReferenceID: ref,
		Status:      providerSettlementStatusFromTransfer(transfer.GetStatus()),
		FinalAmount: railMoneyFromProto(transfer.GetNetAmount()),
	}, nil
}

func (g *providerSettlementGateway) Block(ctx context.Context, req rail.BlockRequest) (rail.RailResult, error) {
	return rail.RailResult{}, merrors.NotImplemented("provider settlement gateway: block not supported")
}

func (g *providerSettlementGateway) Release(ctx context.Context, req rail.ReleaseRequest) (rail.RailResult, error) {
	return rail.RailResult{}, merrors.NotImplemented("provider settlement gateway: release not supported")
}

func buildProviderSettlementDestination(req rail.TransferRequest) *chainv1.TransferDestination {
	destRef := strings.TrimSpace(req.ToAccountID)
	memo := strings.TrimSpace(req.DestinationMemo)
	if destRef == "" && memo == "" {
		return nil
	}
	return &chainv1.TransferDestination{
		Destination: &chainv1.TransferDestination_ExternalAddress{ExternalAddress: destRef},
		Memo:        memo,
	}
}

func providerSettlementStatusFromTransfer(status chainv1.TransferStatus) string {
	switch status {
	case chainv1.TransferStatus_TRANSFER_CONFIRMED:
		return rail.TransferStatusSuccess
	case chainv1.TransferStatus_TRANSFER_FAILED:
		return rail.TransferStatusFailed
	case chainv1.TransferStatus_TRANSFER_CANCELLED:
		return rail.TransferStatusRejected
	default:
		return rail.TransferStatusPending
	}
}

func railMoneyFromProto(src *moneyv1.Money) *rail.Money {
	if src == nil {
		return nil
	}
	currency := strings.TrimSpace(src.GetCurrency())
	amount := strings.TrimSpace(src.GetAmount())
	if currency == "" || amount == "" {
		return nil
	}
	return &rail.Money{
		Amount:   amount,
		Currency: currency,
	}
}
