package sresponse

import (
	"net/http"

	"github.com/tech/sendico/pkg/api/http/response"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/model"
	feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1"
	paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
	oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
	orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
)

type FeeLine struct {
	LedgerAccountRef string            `json:"ledgerAccountRef,omitempty"`
	Amount           *model.Money      `json:"amount,omitempty"`
	LineType         string            `json:"lineType,omitempty"`
	Side             string            `json:"side,omitempty"`
	Meta             map[string]string `json:"meta,omitempty"`
}

type FxQuote struct {
	QuoteRef        string       `json:"quoteRef,omitempty"`
	BaseCurrency    string       `json:"baseCurrency,omitempty"`
	QuoteCurrency   string       `json:"quoteCurrency,omitempty"`
	Side            string       `json:"side,omitempty"`
	Price           string       `json:"price,omitempty"`
	BaseAmount      *model.Money `json:"baseAmount,omitempty"`
	QuoteAmount     *model.Money `json:"quoteAmount,omitempty"`
	ExpiresAtUnixMs int64        `json:"expiresAtUnixMs,omitempty"`
	Provider        string       `json:"provider,omitempty"`
	RateRef         string       `json:"rateRef,omitempty"`
	Firm            bool         `json:"firm,omitempty"`
}

type PaymentQuote struct {
	QuoteRef                 string       `json:"quoteRef,omitempty"`
	DebitAmount              *model.Money `json:"debitAmount,omitempty"`
	ExpectedSettlementAmount *model.Money `json:"expectedSettlementAmount,omitempty"`
	ExpectedFeeTotal         *model.Money `json:"expectedFeeTotal,omitempty"`
	FeeLines                 []FeeLine    `json:"feeLines,omitempty"`
	FxQuote                  *FxQuote     `json:"fxQuote,omitempty"`
}

type PaymentQuoteAggregate struct {
	DebitAmounts              []*model.Money `json:"debitAmounts,omitempty"`
	ExpectedSettlementAmounts []*model.Money `json:"expectedSettlementAmounts,omitempty"`
	ExpectedFeeTotals         []*model.Money `json:"expectedFeeTotals,omitempty"`
}

type PaymentQuotes struct {
	IdempotencyKey string                 `json:"idempotencyKey"`
	QuoteRef       string                 `json:"quoteRef,omitempty"`
	Aggregate      *PaymentQuoteAggregate `json:"aggregate,omitempty"`
	Quotes         []PaymentQuote         `json:"quotes,omitempty"`
}

type Payment struct {
	PaymentRef     string        `json:"paymentRef,omitempty"`
	IdempotencyKey string        `json:"idempotencyKey,omitempty"`
	State          string        `json:"state,omitempty"`
	FailureCode    string        `json:"failureCode,omitempty"`
	FailureReason  string        `json:"failureReason,omitempty"`
	LastQuote      *PaymentQuote `json:"lastQuote,omitempty"`
}

type paymentQuoteResponse struct {
	authResponse   `json:",inline"`
	IdempotencyKey string        `json:"idempotencyKey,omitempty"`
	Quote          *PaymentQuote `json:"quote"`
}

type paymentQuotesResponse struct {
	authResponse `json:",inline"`
	Quote        *PaymentQuotes `json:"quote"`
}

type paymentsResponse struct {
	authResponse `json:",inline"`
	Payments     []Payment                        `json:"payments"`
	Page         *paginationv1.CursorPageResponse `json:"page,omitempty"`
}

type paymentResponse struct {
	authResponse `json:",inline"`
	Payment      *Payment `json:"payment"`
}

// PaymentQuote wraps a payment quote with refreshed access token.
func PaymentQuoteResponse(logger mlogger.Logger, idempotencyKey string, quote *orchestratorv1.PaymentQuote, token *TokenData) http.HandlerFunc {
	return response.Ok(logger, paymentQuoteResponse{
		Quote:          toPaymentQuote(quote),
		IdempotencyKey: idempotencyKey,
		authResponse:   authResponse{AccessToken: *token},
	})
}

// PaymentQuotes wraps batch quotes with refreshed access token.
func PaymentQuotesResponse(logger mlogger.Logger, resp *orchestratorv1.QuotePaymentsResponse, token *TokenData) http.HandlerFunc {
	return response.Ok(logger, paymentQuotesResponse{
		Quote:        toPaymentQuotes(resp),
		authResponse: authResponse{AccessToken: *token},
	})
}

// Payments wraps a list of payments with refreshed access token.
func PaymentsResponse(logger mlogger.Logger, payments []*orchestratorv1.Payment, token *TokenData) http.HandlerFunc {
	return response.Ok(logger, paymentsResponse{
		Payments:     toPayments(payments),
		authResponse: authResponse{AccessToken: *token},
	})
}

// PaymentsList wraps a list of payments with refreshed access token and pagination data.
func PaymentsListResponse(logger mlogger.Logger, resp *orchestratorv1.ListPaymentsResponse, token *TokenData) http.HandlerFunc {
	return response.Ok(logger, paymentsResponse{
		Payments:     toPayments(resp.GetPayments()),
		Page:         resp.GetPage(),
		authResponse: authResponse{AccessToken: *token},
	})
}

// Payment wraps a payment with refreshed access token.
func PaymentResponse(logger mlogger.Logger, payment *orchestratorv1.Payment, token *TokenData) http.HandlerFunc {
	return response.Ok(logger, paymentResponse{
		Payment:      toPayment(payment),
		authResponse: authResponse{AccessToken: *token},
	})
}

func toFeeLines(lines []*feesv1.DerivedPostingLine) []FeeLine {
	if len(lines) == 0 {
		return nil
	}
	result := make([]FeeLine, 0, len(lines))
	for _, line := range lines {
		if line == nil {
			continue
		}
		result = append(result, FeeLine{
			LedgerAccountRef: line.GetLedgerAccountRef(),
			Amount:           toMoney(line.GetMoney()),
			LineType:         line.GetLineType().String(),
			Side:             line.GetSide().String(),
			Meta:             line.GetMeta(),
		})
	}
	if len(result) == 0 {
		return nil
	}
	return result
}

func toFxQuote(q *oraclev1.Quote) *FxQuote {
	if q == nil {
		return nil
	}
	pair := q.GetPair()
	base := ""
	quote := ""
	if pair != nil {
		base = pair.GetBase()
		quote = pair.GetQuote()
	}
	return &FxQuote{
		QuoteRef:        q.GetQuoteRef(),
		BaseCurrency:    base,
		QuoteCurrency:   quote,
		Side:            q.GetSide().String(),
		Price:           q.GetPrice().GetValue(),
		BaseAmount:      toMoney(q.GetBaseAmount()),
		QuoteAmount:     toMoney(q.GetQuoteAmount()),
		ExpiresAtUnixMs: q.GetExpiresAtUnixMs(),
		Provider:        q.GetProvider(),
		RateRef:         q.GetRateRef(),
		Firm:            q.GetFirm(),
	}
}

func toPaymentQuote(q *orchestratorv1.PaymentQuote) *PaymentQuote {
	if q == nil {
		return nil
	}
	return &PaymentQuote{
		QuoteRef:                 q.GetQuoteRef(),
		DebitAmount:              toMoney(q.GetDebitAmount()),
		ExpectedSettlementAmount: toMoney(q.GetExpectedSettlementAmount()),
		ExpectedFeeTotal:         toMoney(q.GetExpectedFeeTotal()),
		FeeLines:                 toFeeLines(q.GetFeeLines()),
		FxQuote:                  toFxQuote(q.GetFxQuote()),
	}
}

func toPaymentQuoteAggregate(q *orchestratorv1.PaymentQuoteAggregate) *PaymentQuoteAggregate {
	if q == nil {
		return nil
	}
	return &PaymentQuoteAggregate{
		DebitAmounts:              toMoneyList(q.GetDebitAmounts()),
		ExpectedSettlementAmounts: toMoneyList(q.GetExpectedSettlementAmounts()),
		ExpectedFeeTotals:         toMoneyList(q.GetExpectedFeeTotals()),
	}
}

func toPaymentQuotes(resp *orchestratorv1.QuotePaymentsResponse) *PaymentQuotes {
	if resp == nil {
		return nil
	}
	quotes := make([]PaymentQuote, 0, len(resp.GetQuotes()))
	for _, quote := range resp.GetQuotes() {
		if dto := toPaymentQuote(quote); dto != nil {
			quotes = append(quotes, *dto)
		}
	}
	if len(quotes) == 0 {
		quotes = nil
	}
	return &PaymentQuotes{
		IdempotencyKey: resp.GetIdempotencyKey(),
		QuoteRef:       resp.GetQuoteRef(),
		Aggregate:      toPaymentQuoteAggregate(resp.GetAggregate()),
		Quotes:         quotes,
	}
}

func toPayments(items []*orchestratorv1.Payment) []Payment {
	if len(items) == 0 {
		return nil
	}
	result := make([]Payment, 0, len(items))
	for _, item := range items {
		if p := toPayment(item); p != nil {
			result = append(result, *p)
		}
	}
	if len(result) == 0 {
		return nil
	}
	return result
}

func toPayment(p *orchestratorv1.Payment) *Payment {
	if p == nil {
		return nil
	}
	return &Payment{
		PaymentRef:     p.GetPaymentRef(),
		IdempotencyKey: p.GetIdempotencyKey(),
		State:          p.GetState().String(),
		FailureCode:    p.GetFailureCode().String(),
		FailureReason:  p.GetFailureReason(),
		LastQuote:      toPaymentQuote(p.GetLastQuote()),
	}
}
