package flutter.guru.guru_applovin_flutter

import android.app.Activity
import android.util.Log
import android.util.SparseArray
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.core.util.valueIterator
import com.amazon.device.ads.*
import com.applovin.mediation.MaxAd
import com.applovin.mediation.MaxAdRevenueListener
import com.applovin.mediation.MaxAdViewAdListener
import com.applovin.mediation.MaxError
import com.applovin.mediation.MaxNetworkResponseInfo
import com.applovin.mediation.ads.MaxAdView
import com.applovin.mediation.ads.MaxInterstitialAd
import com.applovin.sdk.AppLovinAdSize
import com.applovin.sdk.AppLovinSdkUtils
import flutter.guru.guru_applovin_flutter.log.Logger
import io.flutter.plugin.common.MethodChannel

/**
 *  Created by yaqiliu on 2020-03-10.
 */
class BannerAd(id: Int, channel: MethodChannel) : MaxAdViewAdListener, MaxAdRevenueListener {

    private val id: Int = id
    private val channel: MethodChannel = channel
    var status: Int = AdStatus.CREATED

    var anchorOffset = 0.0
    var horizontalCenterOffset = 0.0
    var anchorType = 0

    private var bannerAd: MaxAdView? = null

    @ScreenOrientation
    private var orientation: Int = ScreenOrientation.PORTRAIT

    companion object {

        private val allAds: SparseArray<BannerAd> = SparseArray()

        fun createBanner(
            id: Int,
            channel: MethodChannel,
        ): BannerAd {
            var bannerAd = getAdForId(id)
            if (bannerAd == null) {
                bannerAd = BannerAd(
                    id,
                    channel
                )
            }
            return bannerAd
        }

        fun getAdForId(id: Int): BannerAd? {
            return allAds.get(id)
        }

        fun disposeAll(activity: Activity?) {
            if (activity == null) return
            var iterator = allAds.valueIterator()
            if (iterator.hasNext()) {
                iterator.next().dispose(activity)
            }
        }

        fun orientationChanged(activity: Activity, @ScreenOrientation orientation: Int) {
            var iterator = allAds.valueIterator()
            if (iterator.hasNext()) {
                iterator.next().updateOrientation(activity, orientation)
            }
        }
    }

    init {
        allAds.put(id, this)
        anchorOffset = 0.0
        horizontalCenterOffset = 0.0
        anchorType = 80
    }

    private fun logWarn(message: String) {
        Logger.w("BannerAd", message)
    }

    private fun logDebug(message: String) {
        Logger.d("BannerAd", message)
    }

    private fun logError(message: String) {
        Logger.e("BannerAd", message)
    }

    internal fun load(
        activity: Activity,
        adUnitId: String,
        adAmazonSlotId: String?,
        placement: String? = null
    ) {
        status = AdStatus.LOADING

        bannerAd = MaxAdView(adUnitId, activity).let {
            it.placement = placement ?: "Unknown"
            it.setListener(this)
            it.setRevenueListener(this)
            it
        }

        if (!adAmazonSlotId.isNullOrBlank()) {
            val loader = DTBAdRequest()
            loader.setSizes(DTBAdSize(320, 50, adAmazonSlotId))
            loader.loadAd(object : DTBAdCallback {
                // No APS bid available
                override fun onFailure(adError: AdError) {
                    logDebug("Amazon:Oops banner ad load has failed: ${adError.message}")
                    bannerAd?.apply {
                        setLocalExtraParameter("amazon_ad_error", adError)
                        loadAd()
                    }
                }

                override fun onSuccess(dtbAdResponse: DTBAdResponse) {
                    logDebug("Amazon BannerAd load success")
                    bannerAd?.apply {
                        setLocalExtraParameter("amazon_ad_response", dtbAdResponse)
                        loadAd()
                    }
                }

            })
        } else {
            bannerAd?.loadAd()
        }
    }

    internal fun show(activity: Activity) {
        if (activity.findViewById<ViewGroup>(id) == null) {
            val isTablet = AppLovinSdkUtils.isTablet(activity) // Available on Android SDK 9.6.2+
            val heightPx = AppLovinSdkUtils.dpToPx(activity, if (isTablet) 90 else 50)

            bannerAd!!.setLayoutParams(
                LinearLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    heightPx
                )
            )

            val content = LinearLayout(activity)
            content.id = id
            content.orientation = LinearLayout.VERTICAL
            content.gravity = anchorType
            content.addView(bannerAd)
            val scale = activity.resources.displayMetrics.density
            val left =
                if (horizontalCenterOffset > 0) (horizontalCenterOffset * scale).toInt() else 0
            val right =
                if (horizontalCenterOffset < 0) (Math.abs(horizontalCenterOffset) * scale).toInt() else 0
            if (anchorType == Gravity.BOTTOM) {
                content.setPadding(left, 0, right, (anchorOffset * scale).toInt())
            } else {
                content.setPadding(left, (anchorOffset * scale).toInt(), right, 0)
            }
            activity.addContentView(
                content,
                ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
                )
            )

            bannerAd!!.visibility = View.VISIBLE
            bannerAd!!.startAutoRefresh()
        } else {
            bannerAd!!.startAutoRefresh()
            bannerAd!!.visibility = View.VISIBLE
        }
    }

    internal fun hide(activity: Activity) {
        if (activity.findViewById<ViewGroup>(id) != null) {
            bannerAd!!.stopAutoRefresh()
            bannerAd!!.visibility = View.GONE
        }
    }

    internal fun dispose(activity: Activity) {
        allAds.remove(id)
        bannerAd!!.destroy()

        val contentView = activity.findViewById<ViewGroup>(id)
        if (contentView == null || contentView.parent !is ViewGroup) return

        val contentParent = contentView.parent as ViewGroup
        contentParent.removeView(contentView)
    }

    internal fun updateOrientation(activity: Activity, @ScreenOrientation orientation: Int) {
        val content = activity.findViewById<ViewGroup>(id)
        if (this.orientation != orientation && content != null) {
            val isVisible = bannerAd!!.visibility != View.GONE
            bannerAd!!.stopAutoRefresh()
            bannerAd!!.visibility = View.GONE
            content.removeAllViews()
            (content.parent as ViewGroup).removeView(content)
            if (isVisible) show(activity)
        }
        this.orientation = orientation
    }

    override fun onAdLoadFailed(adUnitId: String, err: MaxError) {
        if (status == AdStatus.LOADED) {
            return
        }
        val waterfallName = try {
            err.waterfall?.let { waterfall ->
                logWarn("Waterfall Name: " + waterfall.name + " and Test Name: " + waterfall.testName)
                logWarn("Waterfall latency was: " + waterfall.latencyMillis + " milliseconds")

                for (networkResponse: MaxNetworkResponseInfo in waterfall.networkResponses) {
                    logWarn(
                        "Network -> " + networkResponse.mediatedNetwork +
                                "...latency: " + networkResponse.latencyMillis +
                                "...credentials: " + networkResponse.credentials + " milliseconds" +
                                "...error: " + networkResponse.error
                    )
                }
                return@let waterfall.name
            } ?: "unknown"
        } catch (throwable: Throwable) {
            logError("onAdLoadFailed error: ${throwable.message}")
            "unknown"
        }

        status = AdStatus.FAILED
        channel.invokeMethod(
            "onBannerAdLoadFailed",
            AdHelp.argumentsMapEx(
                id,
                parameters = mapOf("errorCode" to err.code, "ad_waterfall_name" to waterfallName)
            )
        )
    }

    override fun onAdClicked(ad: MaxAd) {
        channel.invokeMethod("onBannerAdClicked", AdHelp.argumentsMap(id))
    }

    override fun onAdDisplayed(ad: MaxAd) {
        channel.invokeMethod(
            "onBannerAdDisplayed", AdHelp.argumentsMapEx(
                id, parameters = mapOf(
                    "ad_revenue" to (ad.revenue ?: -1.0),
                    "ad_format" to (ad.format?.label ?: ""),
                    "ad_source" to (ad.networkName ?: ""),
                    "ad_unit_name" to (ad.adUnitId ?: "")
                )
            )
        )
    }

    override fun onAdExpanded(ad: MaxAd) {
    }

    override fun onAdCollapsed(ad: MaxAd) {
    }

    override fun onAdLoaded(ad: MaxAd) {
        // Banner ad is ready to be shown. interstitialAd!!.isReady() will now return 'true'
        val waterfallName = try {
            ad.waterfall?.let { waterfall ->
                logWarn("Waterfall Name: " + waterfall.name + " and Test Name: " + waterfall.testName)
                logWarn("Waterfall latency was: " + waterfall.latencyMillis + " milliseconds")

                for (networkResponse: MaxNetworkResponseInfo in waterfall.networkResponses) {
                    logWarn(
                        "Network -> " + networkResponse.mediatedNetwork +
                                "...latency: " + networkResponse.latencyMillis +
                                "...credentials: " + networkResponse.credentials + " milliseconds" +
                                "...error: " + networkResponse.error
                    )
                }
                return@let waterfall.name
            } ?: "unknown"
        } catch (throwable: Throwable) {
            logError("onAdLoadFailed error: ${throwable.message}")
            "unknown"
        }
        val parameters = mapOf(
            "ad_revenue" to (ad.revenue ?: -1.0),
            "ad_format" to (ad.format?.label ?: ""),
            "ad_source" to (ad.networkName ?: ""),
            "ad_unit_name" to (ad.adUnitId ?: ""),
            "ad_waterfall_name" to waterfallName,
        )
        status = AdStatus.LOADED
        channel.invokeMethod("onBannerAdLoaded", AdHelp.argumentsMapEx(id, parameters = parameters))
    }

    override fun onAdHidden(ad: MaxAd) {
        // Interstitial ad is hidden. Pre-load the next ad
        status = AdStatus.HIDDEN
        channel.invokeMethod("onBannerAdHidden", AdHelp.argumentsMap(id))
        Logger.w("Ads", "onBannerAdHidden")
    }

    override fun onAdDisplayFailed(ad: MaxAd, err: MaxError) {
        // Interstitial ad failed to display. We recommend loading the next ad
        status = AdStatus.FAILED
        channel.invokeMethod(
            "onBannerAdDisplayFailed",
            AdHelp.argumentsMap(id, "errorCode", err.code)
        )
    }

    override fun onAdRevenuePaid(ad: MaxAd) {
        channel.invokeMethod(
            "onAdImpression", mapOf(
                "payload" to AdHelp.toAdPayload(ad)
            )
        )
    }

}
