package io.novafoundation.nova.feature_assets.presentation.balance.common.mappers

import io.novafoundation.nova.common.list.GroupedList
import io.novafoundation.nova.common.list.toListWithHeaders
import io.novafoundation.nova.common.presentation.AssetIconProvider
import io.novafoundation.nova.common.presentation.getAssetIconOrFallback
import io.novafoundation.nova.common.utils.formatting.formatAsChange
import io.novafoundation.nova.common.utils.orZero
import io.novafoundation.nova.feature_account_api.presenatation.chain.ChainUi
import io.novafoundation.nova.runtime.ext.displayNameWithAssetStandard
import io.novafoundation.nova.feature_assets.R
import io.novafoundation.nova.feature_account_api.presenatation.chain.getAssetIconOrFallback
import io.novafoundation.nova.feature_assets.domain.common.AssetWithNetwork
import io.novafoundation.nova.feature_assets.domain.common.TokenAssetGroup
import io.novafoundation.nova.feature_assets.domain.common.AssetBalance
import io.novafoundation.nova.feature_assets.presentation.balance.list.model.items.BalanceListRvItem
import io.novafoundation.nova.feature_assets.presentation.balance.list.model.items.TokenAssetUi
import io.novafoundation.nova.feature_assets.presentation.balance.list.model.items.TokenGroupUi
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.formatAmountToAmountModel
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.model.AmountConfig
import io.novafoundation.nova.common.presentation.masking.formatter.MaskableValueFormatter
import io.novafoundation.nova.feature_wallet_api.presentation.model.FractionPartStyling

class TokenAssetFormatterFactory(
    private val amountFormatter: AmountFormatter
) {
    fun create(maskableFormatter: MaskableValueFormatter): TokenAssetFormatter {
        return TokenAssetFormatter(maskableFormatter, amountFormatter)
    }
}

class TokenAssetFormatter(
    private val maskableFormatter: MaskableValueFormatter,
    private val amountFormatter: AmountFormatter
) : CommonAssetFormatter(maskableFormatter, amountFormatter) {

    fun mapGroupedAssetsToUi(
        groupedTokens: GroupedList<TokenAssetGroup, AssetWithNetwork>,
        assetIconProvider: AssetIconProvider,
        assetFilter: (groupId: String, List<TokenAssetUi>) -> List<TokenAssetUi> = { _, assets -> assets },
        groupBalance: (TokenAssetGroup) -> AssetBalance.Amount = { it.groupBalance.total },
        balance: (AssetBalance) -> AssetBalance.Amount = AssetBalance::total,
    ): List<BalanceListRvItem> {
        return groupedTokens.mapKeys { (group, assets) -> mapTokenAssetGroupToUi(assetIconProvider, group, assets, groupBalance) }
            .mapValues { (group, assets) ->
                val assetModels = mapAssetsToAssetModels(assetIconProvider, group, assets, balance)
                assetFilter(group.itemId, assetModels)
            }
            .toListWithHeaders()
            .filterIsInstance<BalanceListRvItem>()
    }

    fun mapTokenAssetGroupToUi(
        assetIconProvider: AssetIconProvider,
        assetGroup: TokenAssetGroup,
        assets: List<AssetWithNetwork>,
        groupBalance: (TokenAssetGroup) -> AssetBalance.Amount = { it.groupBalance.total }
    ): TokenGroupUi {
        val balance = groupBalance(assetGroup)
        return TokenGroupUi(
            itemId = assetGroup.groupId,
            tokenIcon = assetIconProvider.getAssetIconOrFallback(assetGroup.tokenInfo.icon),
            rate = mapCoinRateChange(assetGroup.tokenInfo.coinRate, assetGroup.tokenInfo.currency),
            recentRateChange = assetGroup.tokenInfo.coinRate?.recentRateChange.orZero().formatAsChange(),
            rateChangeColorRes = mapCoinRateChangeColorRes(assetGroup.tokenInfo.coinRate),
            tokenSymbol = assetGroup.tokenInfo.symbol.value,
            singleItemGroup = assetGroup.itemsCount <= 1,
            balance = maskableFormatter.format {
                amountFormatter.formatAmountToAmountModel(
                    amount = balance.amount,
                    token = assetGroup.tokenInfo.token,
                    config = AmountConfig(
                        includeAssetTicker = false,
                        tokenFractionPartStyling = FractionPartStyling.Styled(R.dimen.asset_balance_fraction_size)
                    )
                )
            },
            groupType = mapType(assets)
        )
    }

    private fun mapAssetsToAssetModels(
        assetIconProvider: AssetIconProvider,
        group: TokenGroupUi,
        assets: List<AssetWithNetwork>,
        balance: (AssetBalance) -> AssetBalance.Amount
    ): List<TokenAssetUi> {
        return assets.map {
            TokenAssetUi(
                group.getId(),
                mapAssetToAssetModel(it.asset, balance(it.balanceWithOffChain)),
                assetIconProvider.getAssetIconOrFallback(it.asset.token.configuration),
                // Not mapChainToUi() here - this row's subtitle needs to disambiguate which issuance of the
                // token this is (e.g. "Ethereum (ERC-20)" vs "Tron (TRC-20)"), which a bare chain name alone
                // doesn't when multiple ecosystems share the same symbol (USDT, USDC, etc.).
                ChainUi(
                    id = it.chain.id,
                    name = it.chain.displayNameWithAssetStandard(),
                    icon = it.chain.icon
                )
            )
        }
    }

    private fun mapType(
        assets: List<AssetWithNetwork>,
    ): TokenGroupUi.GroupType {
        return if (assets.size == 1) {
            TokenGroupUi.GroupType.SingleItem(assets.first().asset.token.configuration)
        } else {
            TokenGroupUi.GroupType.Group
        }
    }
}
