package sresponse

import (
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/tech/sendico/pkg/api/http/response"
	"github.com/tech/sendico/pkg/mlogger"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
	connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
	"google.golang.org/protobuf/types/known/timestamppb"
)

type walletAsset struct {
	Chain           string `json:"chain"`
	TokenSymbol     string `json:"tokenSymbol"`
	ContractAddress string `json:"contractAddress"`
}

type wallet struct {
	WalletRef       string            `json:"walletRef"`
	OrganizationRef string            `json:"organizationRef"`
	OwnerRef        string            `json:"ownerRef"`
	Asset           walletAsset       `json:"asset"`
	DepositAddress  string            `json:"depositAddress"`
	Status          string            `json:"status"`
	Metadata        map[string]string `json:"metadata,omitempty"`
	Name            string            `json:"name"`
	Description     *string           `json:"description,omitempty"`
	CreatedAt       string            `json:"createdAt,omitempty"`
	UpdatedAt       string            `json:"updatedAt,omitempty"`
}

type walletsResponse struct {
	authResponse `json:",inline"`
	Wallets      []wallet                         `json:"wallets"`
	Page         *paginationv1.CursorPageResponse `json:"page,omitempty"`
}

type walletBalance struct {
	Available       *paymenttypes.Money `json:"available,omitempty"`
	PendingInbound  *paymenttypes.Money `json:"pendingInbound,omitempty"`
	PendingOutbound *paymenttypes.Money `json:"pendingOutbound,omitempty"`
	CalculatedAt    string              `json:"calculatedAt,omitempty"`
}

type walletBalanceResponse struct {
	authResponse `json:",inline"`
	Balance      walletBalance `json:"balance"`
}

func Wallets(logger mlogger.Logger, resp *chainv1.ListManagedWalletsResponse, accessToken *TokenData) http.HandlerFunc {
	dto := walletsResponse{
		Page:         resp.GetPage(),
		authResponse: authResponse{AccessToken: *accessToken},
	}
	dto.Wallets = make([]wallet, 0, len(resp.GetWallets()))
	for _, w := range resp.GetWallets() {
		dto.Wallets = append(dto.Wallets, toWallet(w))
	}
	return response.Ok(logger, dto)
}

func WalletBalance(logger mlogger.Logger, bal *chainv1.WalletBalance, accessToken *TokenData) http.HandlerFunc {
	return response.Ok(logger, walletBalanceResponse{
		Balance:      toWalletBalance(bal),
		authResponse: authResponse{AccessToken: *accessToken},
	})
}

func toWallet(w *chainv1.ManagedWallet) wallet {
	if w == nil {
		return wallet{}
	}
	asset := w.GetAsset()
	chain := ""
	token := ""
	contract := ""
	if asset != nil {
		chain = chainNetworkValue(asset.GetChain())
		token = asset.GetTokenSymbol()
		contract = asset.GetContractAddress()
	}
	name := ""
	if d := w.GetDescribable(); d != nil {
		name = strings.TrimSpace(d.GetName())
	}
	if name == "" {
		name = strings.TrimSpace(w.GetMetadata()["name"])
	}
	if name == "" {
		name = w.GetWalletRef()
	}
	var description *string
	if d := w.GetDescribable(); d != nil && d.Description != nil {
		if trimmed := strings.TrimSpace(d.GetDescription()); trimmed != "" {
			description = &trimmed
		}
	}
	if description == nil {
		if trimmed := strings.TrimSpace(w.GetMetadata()["description"]); trimmed != "" {
			description = &trimmed
		}
	}
	return wallet{
		WalletRef:       w.GetWalletRef(),
		OrganizationRef: w.GetOrganizationRef(),
		OwnerRef:        w.GetOwnerRef(),
		Asset: walletAsset{
			Chain:           chain,
			TokenSymbol:     token,
			ContractAddress: contract,
		},
		DepositAddress: w.GetDepositAddress(),
		Status:         w.GetStatus().String(),
		Metadata:       w.GetMetadata(),
		Name:           name,
		Description:    description,
		CreatedAt:      tsToString(w.GetCreatedAt()),
		UpdatedAt:      tsToString(w.GetUpdatedAt()),
	}
}

func toWalletBalance(b *chainv1.WalletBalance) walletBalance {
	if b == nil {
		return walletBalance{}
	}
	return walletBalance{
		Available:       toMoney(b.GetAvailable()),
		PendingInbound:  toMoney(b.GetPendingInbound()),
		PendingOutbound: toMoney(b.GetPendingOutbound()),
		CalculatedAt:    tsToString(b.GetCalculatedAt()),
	}
}

func tsToString(ts *timestamppb.Timestamp) string {
	if ts == nil {
		return ""
	}
	return ts.AsTime().UTC().Format(time.RFC3339)
}

func chainNetworkValue(chain chainv1.ChainNetwork) string {
	name := chain.String()
	if !strings.HasPrefix(name, "CHAIN_NETWORK_") {
		return "unspecified"
	}
	trimmed := strings.TrimPrefix(name, "CHAIN_NETWORK_")
	if trimmed == "" {
		return "unspecified"
	}
	return strings.ToLower(trimmed)
}

// WalletsFromAccounts converts connector accounts to wallet response format.
// Used when querying multiple gateways via discovery.
func WalletsFromAccounts(logger mlogger.Logger, accounts []*connectorv1.Account, accessToken *TokenData) http.HandlerFunc {
	dto := walletsResponse{
		authResponse: authResponse{AccessToken: *accessToken},
	}
	dto.Wallets = make([]wallet, 0, len(accounts))
	for _, acc := range accounts {
		if acc == nil {
			continue
		}
		dto.Wallets = append(dto.Wallets, accountToWallet(acc))
	}
	return response.Ok(logger, dto)
}

func accountToWallet(acc *connectorv1.Account) wallet {
	if acc == nil {
		return wallet{}
	}

	// Extract wallet details from provider details
	details := map[string]interface{}{}
	if acc.GetProviderDetails() != nil {
		details = acc.GetProviderDetails().AsMap()
	}

	walletRef := ""
	if ref := acc.GetRef(); ref != nil {
		walletRef = strings.TrimSpace(ref.GetAccountId())
	}
	if v := stringFromDetails(details, "wallet_ref"); v != "" {
		walletRef = v
	}

	organizationRef := stringFromDetails(details, "organization_ref")
	ownerRef := strings.TrimSpace(acc.GetOwnerRef())
	if v := stringFromDetails(details, "owner_ref"); v != "" {
		ownerRef = v
	}

	chain := stringFromDetails(details, "network")
	tokenSymbol := stringFromDetails(details, "token_symbol")
	contractAddress := stringFromDetails(details, "contract_address")
	depositAddress := stringFromDetails(details, "deposit_address")

	name := ""
	if d := acc.GetDescribable(); d != nil {
		name = strings.TrimSpace(d.GetName())
	}
	if name == "" {
		name = strings.TrimSpace(acc.GetLabel())
	}
	if name == "" {
		name = walletRef
	}

	var description *string
	if d := acc.GetDescribable(); d != nil && d.Description != nil {
		if trimmed := strings.TrimSpace(d.GetDescription()); trimmed != "" {
			description = &trimmed
		}
	}

	status := acc.GetState().String()
	// Convert connector state to wallet status format
	switch acc.GetState() {
	case connectorv1.AccountState_ACCOUNT_ACTIVE:
		status = "MANAGED_WALLET_ACTIVE"
	case connectorv1.AccountState_ACCOUNT_SUSPENDED:
		status = "MANAGED_WALLET_SUSPENDED"
	case connectorv1.AccountState_ACCOUNT_CLOSED:
		status = "MANAGED_WALLET_CLOSED"
	}

	return wallet{
		WalletRef:       walletRef,
		OrganizationRef: organizationRef,
		OwnerRef:        ownerRef,
		Asset: walletAsset{
			Chain:           chain,
			TokenSymbol:     tokenSymbol,
			ContractAddress: contractAddress,
		},
		DepositAddress: depositAddress,
		Status:         status,
		Name:           name,
		Description:    description,
		CreatedAt:      tsToString(acc.GetCreatedAt()),
		UpdatedAt:      tsToString(acc.GetUpdatedAt()),
	}
}

func stringFromDetails(details map[string]interface{}, key string) string {
	if details == nil {
		return ""
	}
	if value, ok := details[key]; ok {
		return strings.TrimSpace(fmt.Sprint(value))
	}
	return ""
}

// WalletBalanceFromConnector converts connector balance to wallet balance response format.
// Used when querying gateways via discovery.
func WalletBalanceFromConnector(logger mlogger.Logger, bal *connectorv1.Balance, accessToken *TokenData) http.HandlerFunc {
	return response.Ok(logger, walletBalanceResponse{
		Balance:      connectorBalanceToWalletBalance(bal),
		authResponse: authResponse{AccessToken: *accessToken},
	})
}

func connectorBalanceToWalletBalance(b *connectorv1.Balance) walletBalance {
	if b == nil {
		return walletBalance{}
	}
	return walletBalance{
		Available:       connectorMoneyToModel(b.GetAvailable()),
		PendingInbound:  connectorMoneyToModel(b.GetPendingInbound()),
		PendingOutbound: connectorMoneyToModel(b.GetPendingOutbound()),
		CalculatedAt:    tsToString(b.GetCalculatedAt()),
	}
}

func connectorMoneyToModel(m *moneyv1.Money) *paymenttypes.Money {
	if m == nil {
		return nil
	}
	return &paymenttypes.Money{
		Amount:   m.GetAmount(),
		Currency: m.GetCurrency(),
	}
}
