using System;
using Guru;
using UnityEngine;

namespace Guru
{
    public abstract class ADServiceBase<T> : IADService where T : new()
    {
        // 单利定义
        private static T _instance;

        public static T Instance
        {
            get
            {
                if (null == _instance) _instance = new T();
                return _instance;
            }
        }

        protected static readonly string Tag = "[Ads]";
        public bool IsInitialized => MaxSdk.IsInitialized() || _isServiceStarted;
        protected bool IsNetworkEnabled => Application.internetReachability != NetworkReachability.NotReachable;

        private bool _isServiceStarted;
        protected bool _isDebugMode;
        protected bool _isAutoLoadAds;

        private Action _onSdkInitReady;
        
        public static Action OnBannerLoaded;

        public static Action OnInterstitialLoaded;
        public static Action OnInterstitialFailed;

        public static Action OnRewardLoaded;
        public static Action OnRewardFailed;

        private AdsModel _model;

        /// <summary>
        /// 启动广告服务
        /// </summary>
        /// <param name="callback">广告初始化回调</param>
        /// <param name="autoLoadAds">自动启动广告加载</param>
        /// <param name="isDebugMode">debug模式</param>
        public virtual void StartService(Action callback = null, bool autoLoadAds = true, bool isDebugMode = false)
        {
            if (IsInitialized) return; // 已经初始化后, 无需再次初始化

            _isServiceStarted = true;
            _isAutoLoadAds = autoLoadAds;
            _isDebugMode = isDebugMode;
            _onSdkInitReady = callback;
            _model = AdsModel.Create();
            this.Log("AD SDK Start Init");

            //--------------  初始化回调 ------------------
            MaxSdkCallbacks.OnSdkInitializedEvent += OnMaxSdkInitializedCallBack;
            MaxSdkCallbacks.Interstitial.OnAdRevenuePaidEvent += OnAdRevenuePaidEvent;
            MaxSdkCallbacks.Rewarded.OnAdRevenuePaidEvent += OnAdRevenuePaidEvent;
            MaxSdkCallbacks.Banner.OnAdRevenuePaidEvent += OnAdRevenuePaidEvent;
            MaxSdkCallbacks.MRec.OnAdRevenuePaidEvent += OnAdRevenuePaidEvent;
            //--------------- Banner 回调 -----------------
            MaxSdkCallbacks.Banner.OnAdLoadedEvent += OnBannerLoadedEvent;
            MaxSdkCallbacks.Banner.OnAdLoadFailedEvent += OnBannerFailedEvent;
            MaxSdkCallbacks.Banner.OnAdClickedEvent += OnBannerClickedEvent;
            //--------------- IV 回调 -----------------
            MaxSdkCallbacks.Interstitial.OnAdLoadedEvent += OnInterstitialLoadedEvent;
            MaxSdkCallbacks.Interstitial.OnAdLoadFailedEvent += OnInterstitialFailedEvent;
            MaxSdkCallbacks.Interstitial.OnAdDisplayFailedEvent += InterstitialFailedToDisplayEvent;
            MaxSdkCallbacks.Interstitial.OnAdClickedEvent += OnInterstitialClickEvent;
            MaxSdkCallbacks.Interstitial.OnAdDisplayedEvent += OnInterstitialDisplayEvent;
            MaxSdkCallbacks.Interstitial.OnAdHiddenEvent += OnInterstitialDismissedEvent;
            //--------------- RV 回调 -----------------
            MaxSdkCallbacks.Rewarded.OnAdLoadedEvent += OnRewardedAdLoadedEvent;
            MaxSdkCallbacks.Rewarded.OnAdLoadFailedEvent += OnRewardedAdFailedEvent;
            MaxSdkCallbacks.Rewarded.OnAdDisplayFailedEvent += OnRewardedAdFailedToDisplayEvent;
            MaxSdkCallbacks.Rewarded.OnAdDisplayedEvent += OnRewardedAdDisplayedEvent;
            MaxSdkCallbacks.Rewarded.OnAdClickedEvent += OnRewardedAdClickedEvent;
            MaxSdkCallbacks.Rewarded.OnAdHiddenEvent += OnRewardedAdDismissedEvent;
            MaxSdkCallbacks.Rewarded.OnAdReceivedRewardEvent += OnRewardedAdReceivedRewardEvent;

            //-------------- SDK 初始化 -------------------
            MaxSdk.SetVerboseLogging(_isDebugMode);
            InitService();
        }

        protected virtual void InitService()
        {
        }


        private void OnMaxSdkInitializedCallBack(MaxSdkBase.SdkConfiguration sdkConfiguration)
        {
            this.Log("AD SDK Init Success");
            if (_isAutoLoadAds) OnMaxSdkReady();
            _onSdkInitReady?.Invoke();
        }

        protected virtual void OnMaxSdkReady()
        {
            //TODO:各个项目根据自己情况进行修改，模版默认做法是SDK初始化完成后就进行广告请求
            RequestBannerAD();
            RequestInterstitialAD();
            RequestRewardedAD();
        }

        /// <summary>
        /// 可加载广告
        /// </summary>
        /// <returns></returns>
        public virtual bool CanLoadAds()
        {
            return IsInitialized && IsNetworkEnabled;
        }

        #region ILRD

        private double TchAD001RevValue
        {
            get => _model.TchAD001RevValue;
            set => _model.TchAD001RevValue = value;
        }

        private double TchAD02RevValue
        {
            get => _model.TchAD02RevValue;
            set => _model.TchAD02RevValue = value;
        }

        public void OnAdRevenuePaidEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
        {
            if (adInfo == null) return;
            
            // #1. ad_impression
            OnAdImpression(adInfo);
            
            // #2. tch_ad_rev_roas calculation
            double revenue = adInfo.Revenue;
            CalcTaichi001Value(revenue);
            CalcTaichi02Value(revenue);
            
            // #3. Adjust ad_revenue
            AdjustService.TrackADRevenue(adInfo);
        }

        /// <summary>
        /// 广告ARO收益打点
        /// </summary>
        /// <param name="adInfo"></param>
        private void OnAdImpression(MaxSdkBase.AdInfo adInfo)
        {
            Analytics.ADImpression(adInfo);
        }


        /// <summary>
        /// 计算太极001收益
        /// </summary>
        /// <param name="revenue"></param>
        private void CalcTaichi001Value(double revenue)
        {
            TchAD001RevValue += revenue;
            double revenueValue = TchAD001RevValue;
            Debug.Log($"[TaichConfig] get <TchAD001RevValue> totally: {revenueValue}");
            if (revenueValue >= Analytics.Tch001TargetValue)
            {
                Debug.Log($"[TaichConfig] call <tch_ad_rev_roas_001> with value: {revenueValue}");
                Analytics.Tch001ADRev(revenueValue);
                TchAD001RevValue = 0.0;
            }
        }

        /// <summary>
        /// 计算太极02收益
        /// </summary>
        /// <param name="revenue"></param>
        private void CalcTaichi02Value(double revenue)
        {
            if (!Analytics.EnableTch02Event) return;

            TchAD02RevValue += revenue;
            double revenueValue = TchAD02RevValue;
            Debug.Log($"[Ads] get <TchAD02RevValue> totally: {revenueValue}");
            if (revenueValue >= Analytics.Tch02TargetValue)
            {
                Debug.Log($"[Ads] call <tch_ad_rev_roas_02> with value: {revenueValue}");
                Analytics.Tch02ADRev(revenueValue);
                TchAD02RevValue = 0.0;
            }
        }

        #endregion

        #region Banner Ads

        private string _backColorStr = "#50A436";
        private Color _backColor = new Color(0, 0, 0, 0);
        private string _badsCategory;
        protected float _badsloadStartTime = 0;


        private int GetAdsLoadDuration(ref float startTime)
        {
            int duration = (int)((Time.realtimeSinceStartup - startTime) * 1000);
            startTime = 0;
            return duration;
        }

        public virtual void RequestBannerAD()
        {
            LoadMaxBannerAd();
        }

        /// <summary>
        /// Banner MAX 加载方式
        /// </summary>
        protected void LoadMaxBannerAd()
        {
            OnLoadBads();
            // Banners are automatically sized to 320x50 on phones and 728x90 on tablets
            // You may use the utility method `MaxSdkUtils.isTablet()` to help with view sizing adjustments
            var id = GetBannerID();
            MaxSdk.CreateBanner(id, MaxSdkBase.BannerPosition.BottomCenter);
            MaxSdk.SetBannerExtraParameter(id, "adaptive_banner", "false");
            // Set background or background color for banners to be fully functional
            MaxSdk.SetBannerBackgroundColor(id, _backColor);
            // Analytics.ADBadsLoad(GetBannerID());
            Analytics.ADBadsLoad(AdParams.Build(id));
        }

        public void OnLoadBads()
        {
            _badsloadStartTime = Time.realtimeSinceStartup;
        }


        public virtual void ShowBanner(string category = "")
        {
            _badsCategory = category;
            string adUnitId = GetBannerID();
            MaxSdk.ShowBanner(adUnitId);
            MaxSdk.SetBannerBackgroundColor(adUnitId, _backColor);
            OnBannerImpEvent(adUnitId);
        }

        public virtual void HideBanner()
        {
            MaxSdk.HideBanner(GetBannerID());
        }

        private void OnBannerLoadedEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
        {
            // Analytics.ADBadsLoaded(adUnitId, GetAdsLoadDuration(ref _badsloadStartTime), _badsCategory);
            Analytics.ADBadsLoaded(AdParams.Build(adUnitId, adInfo,
                duration: GetAdsLoadDuration(ref _badsloadStartTime), category: _badsCategory));
            
            OnBannerLoaded?.Invoke();
        }

        private void OnBannerFailedEvent(string adUnitId, MaxSdkBase.ErrorInfo errorInfo)
        {
            // Analytics.ADBadsFailed(adUnitId, (int)errorInfo.Code, GetAdsLoadDuration(ref _badsloadStartTime), _badsCategory);
            Analytics.ADBadsFailed(AdParams.Build(adUnitId,
                duration: GetAdsLoadDuration(ref _badsloadStartTime), category: _badsCategory,
                errorCode: (int)errorInfo.Code,
                waterfallName: errorInfo?.WaterfallInfo?.Name ?? ""));
        }

        private void OnBannerClickedEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
        {
            // Analytics.ADBadsClick(adUnitId, _badsCategory);
            Analytics.ADBadsClick(AdParams.Build(adUnitId, adInfo, _badsCategory));
        }

        private void OnBannerImpEvent(string adUnitId)
        {
            // Analytics.ADBadsClick(adUnitId, _badsCategory);
            Analytics.ADBadsImp(AdParams.Build(adUnitId, category: _badsCategory));
        }

        #endregion

        #region Interstitial Ads

        private string _iadsCategory = "main";
        private int _interstitialRetryAttempt;
        protected float _iadsLoadStartTime;
        private Action _interstitialDismissAction;

        public virtual void RequestInterstitialAD()
        {
            if (!CanLoadAds())
                return;

            LoadMaxInterstitial();
        }


        protected void LoadMaxInterstitial()
        {
            OnLoadIads();
            var id = GetInterstitialID();
            Analytics.ADIadsLoad(AdParams.Build(id));
            MaxSdk.LoadInterstitial(id);
        }

        public void OnLoadIads()
        {
            _iadsLoadStartTime = Time.realtimeSinceStartup;
        }


        public bool IsInterstitialADReady()
        {
            if (!IsInitialized)
                return false;

            return MaxSdk.IsInterstitialReady(GetInterstitialID());
        }

        /// <summary>
        /// 显示插屏广告
        /// </summary>
        /// <param name="rewardAction">广告奖励回调</param>
        /// <param name="failAction">广告失败回调</param>
        /// <param name="dismissAction">广告界面关闭回调</param>
        public virtual void ShowInterstitialAD(string category, Action dismissAction = null)
        {
            if (!IsInitialized)
            {
                this.LogWarning("广告未初始化完成，无法显示插屏广告");
                return;
            }

            if (!IsInterstitialADReady())
            {
                this.LogWarning("插屏没有加载准备好，无法显示插屏广告");
                return;
            }

            _iadsCategory = category;
            _interstitialDismissAction = dismissAction;
            MaxSdk.ShowInterstitial(GetInterstitialID());

            RequestInterstitialAD(); // 直接加载下一个广告
        }

        protected virtual void OnInterstitialLoadedEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
        {
            // Interstitial ad is ready to be shown. MaxSdk.IsInterstitialReady(interstitialAdUnitId) will now return 'true'
            // Reset retry attempt
            // Analytics.ADIadsLoaded(adUnitId, GetAdsLoadDuration(ref _iadsLoadStartTime), _iadsCategory);
            Analytics.ADIadsLoaded(AdParams.Build(adUnitId,
                duration: GetAdsLoadDuration(ref _iadsLoadStartTime), category: _iadsCategory));
            _interstitialRetryAttempt = 0;

            OnInterstitialLoaded?.Invoke();
        }

        protected virtual void OnInterstitialFailedEvent(string adUnitId, MaxSdkBase.ErrorInfo errorInfo)
        {
            // Interstitial ad failed to load 
            // We recommend retrying with exponentially higher delays up to a maximum delay (in this case 64 seconds)
            this.LogError(
                $"OnInterstitialFailedEvent AdLoadFailureInfo:{errorInfo.AdLoadFailureInfo}, Message: {errorInfo.Message}");
            _interstitialRetryAttempt++;
            double retryDelay = Math.Pow(2, Math.Min(3, _interstitialRetryAttempt));
            DelayCall((float)retryDelay, RequestInterstitialAD);
            // Analytics.ADIadsFailed(adUnitId, (int)errorInfo.Code, GetAdsLoadDuration(ref _iadsLoadStartTime), _iadsCategory);
            Analytics.ADIadsFailed(AdParams.Build(adUnitId,
                duration: GetAdsLoadDuration(ref _iadsLoadStartTime), category: _iadsCategory,
                errorCode: (int)errorInfo.Code,
                waterfallName: errorInfo?.WaterfallInfo?.Name ?? ""));

            OnInterstitialFailed?.Invoke();
        }

        protected virtual void InterstitialFailedToDisplayEvent(string adUnitId, MaxSdkBase.ErrorInfo errorInfo,
            MaxSdkBase.AdInfo adInfo)
        {
            // Interstitial ad failed to display. We recommend loading the next ad
            this.LogError(
                $"InterstitialFailedToDisplayEvent AdLoadFailureInfo:{errorInfo.AdLoadFailureInfo}, Message: {errorInfo.Message}");
            // Analytics.ADIadsFailed(adUnitId, (int)errorInfo.Code, GetAdsLoadDuration(ref _iadsLoadStartTime), _iadsCategory);
            Analytics.ADIadsFailed(AdParams.Build(adUnitId,
                duration: GetAdsLoadDuration(ref _iadsLoadStartTime), category: _iadsCategory,
                errorCode: (int)errorInfo.Code,
                waterfallName: errorInfo?.WaterfallInfo?.Name ?? ""));
            DelayCall(2.0f, RequestInterstitialAD);
        }

        protected virtual void OnInterstitialDisplayEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
        {
            // Analytics.ADIadsImp(adUnitId, _iadsCategory);
            Analytics.ADIadsImp(AdParams.Build(adUnitId, category: _iadsCategory));
        }

        protected virtual void OnInterstitialClickEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
        {
            // Analytics.ADIadsClick(adUnitId, _iadsCategory);
            Analytics.ADIadsClick(AdParams.Build(adUnitId, category: _iadsCategory));
        }

        protected virtual void OnInterstitialDismissedEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
        {
            // Interstitial ad is hidden. Pre-load the next ad
            _interstitialDismissAction?.Invoke();
            // Analytics.ADIadsClose(adUnitId, _iadsCategory);
            Analytics.ADIadsClose(AdParams.Build(adUnitId, category: _iadsCategory));
            //延时加载下一个广告
            DelayCall(2.0f, RequestInterstitialAD);
        }

        #endregion

        #region Rewarded Ads

        private string _rewardCategory = "main";
        private int _rewardRetryAttempt;
        protected float _radsLoadStartTime;
        private Action _rewardAction;
        private Action<string> _failAction;
        private Action _dismissAction;

        public virtual void RequestRewardedAD()
        {
            if (!IsInitialized)
                return;

            LoadMaxRewardAd();
        }


        /// <summary>
        /// 默认加载 MAX 广告逻辑
        /// </summary>
        protected void LoadMaxRewardAd(string unitId = "")
        {
            OnLoadRads();
            var id = GetRewardedVideoID();
            Analytics.ADRadsLoad(AdParams.Build(id)); // 上报打点
            MaxSdk.LoadRewardedAd(id);
        }

        public void OnLoadRads()
        {
            _radsLoadStartTime = Time.realtimeSinceStartup;
        }


        public virtual bool IsRewardedADReady()
        {
            if (!IsInitialized)
                return false;

            return MaxSdk.IsRewardedAdReady(GetRewardedVideoID());
        }

        /// <summary>
        /// 显示激励视频广告
        /// </summary>
        /// <param name="rewardAction">广告奖励回调</param>
        /// <param name="failAction">广告失败回调</param>
        /// <param name="dismissAction">广告界面关闭回调</param>
        public virtual void ShowRewardedAD(string category, Action rewardAction = null,
            Action<string> failAction = null, Action dismissAction = null)
        {
            if (!IsInitialized)
            {
                this.LogWarning("广告未初始化完成，无法显示视频广告");
                return;
            }

            if (!IsRewardedADReady())
            {
                this.LogWarning("广告没有准备好，无法显示视频广告");
                return;
            }

            _rewardCategory = category;
            _rewardAction = rewardAction;
            _failAction = failAction;
            _dismissAction = dismissAction;
            MaxSdk.ShowRewardedAd(GetRewardedVideoID());

            RequestRewardedAD();
        }

        protected virtual void OnRewardedAdLoadedEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
        {
            // Rewarded ad is ready to be shown. MaxSdk.IsRewardedAdReady(rewardedAdUnitId) will now return 'true'
            // Reset retry attempt
            // this.Log("OnRewardedAdLoadedEvent");
            // Analytics.ADRadsLoaded(adUnitId, GetAdsLoadDuration(ref _radsLoadStartTime), _rewardCategory);
            Analytics.ADRadsLoaded(AdParams.Build(adUnitId,
                duration: GetAdsLoadDuration(ref _iadsLoadStartTime), category: _iadsCategory));
            _rewardRetryAttempt = 0;

            OnRewardLoaded?.Invoke();
        }

        protected virtual void OnRewardedAdFailedEvent(string adUnitId, MaxSdkBase.ErrorInfo errorInfo)
        {
            // Rewarded ad failed to load 
            // We recommend retrying with exponentially higher delays up to a maximum delay (in this case 64 seconds)
            this.LogError(
                $"OnRewardedAdFailedEvent AdLoadFailureInfo:{errorInfo.AdLoadFailureInfo}, Message: {errorInfo.Message}");
            // Analytics.ADRadsFailed(adUnitId, (int)errorInfo.Code, GetAdsLoadDuration(ref _radsLoadStartTime), _rewardCategory);
            Analytics.ADRadsFailed(AdParams.Build(adUnitId,
                duration: GetAdsLoadDuration(ref _radsLoadStartTime), category: _rewardCategory,
                errorCode: (int)errorInfo.Code,
                waterfallName: errorInfo?.WaterfallInfo?.Name ?? ""));
            _rewardRetryAttempt++;
            double retryDelay = Math.Pow(2, Math.Min(3, _rewardRetryAttempt));
            DelayCall((float)retryDelay, RequestRewardedAD);

            OnRewardFailed?.Invoke();
        }

        protected virtual void OnRewardedAdFailedToDisplayEvent(string adUnitId, MaxSdkBase.ErrorInfo errorInfo,
            MaxSdkBase.AdInfo arg3)
        {
            // Rewarded ad failed to display. We recommend loading the next ad
            this.LogError(
                $"OnRewardedAdFailedToDisplayEvent AdLoadFailureInfo:{errorInfo.AdLoadFailureInfo}, Message: {errorInfo.Message}");
            // Analytics.ADRadsFailed(adUnitId, (int)errorInfo.Code, GetAdsLoadDuration(ref _radsLoadStartTime), _rewardCategory);
            Analytics.ADRadsFailed(AdParams.Build(adUnitId,
                duration: GetAdsLoadDuration(ref _radsLoadStartTime), category: _rewardCategory,
                errorCode: (int)errorInfo.Code,
                waterfallName: errorInfo?.WaterfallInfo?.Name ?? ""));
            _failAction?.Invoke("OnRewardedAdFailedToDisplayEvent");
            DelayCall(2.0f, RequestRewardedAD);

            OnRewardFailed?.Invoke();
        }

        protected virtual void OnRewardedAdDisplayedEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
        {
            this.Log("OnRewardedAdDisplayedEvent");
            // Analytics.ADRadsImp(adUnitId, _rewardCategory);
            Analytics.ADRadsImp(AdParams.Build(adUnitId, category: _rewardCategory));
        }

        protected virtual void OnRewardedAdClickedEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
        {
            this.Log("OnRewardedAdClickedEvent");
            // Analytics.ADRadsClick(adUnitId, _rewardCategory);
            Analytics.ADRadsClick(AdParams.Build(adUnitId, category: _rewardCategory));
        }

        protected virtual void OnRewardedAdDismissedEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
        {
            this.Log("OnRewardedAdDismissedEvent");
            // Analytics.ADRadsClose(adUnitId, _rewardCategory);
            Analytics.ADRadsClose(AdParams.Build(adUnitId, category: _rewardCategory));
            _dismissAction?.Invoke();
            //延时加载下一个广告
            DelayCall(2.0f, RequestRewardedAD);
        }

        protected virtual void OnRewardedAdReceivedRewardEvent(string adUnitId, MaxSdk.Reward reward,
            MaxSdkBase.AdInfo arg3)
        {
            this.Log("OnRewardedAdReceivedRewardEvent");
            // Analytics.ADRadsRewarded(adUnitId, _rewardCategory);
            Analytics.ADRadsRewarded(AdParams.Build(adUnitId, category: _rewardCategory));
            // Rewarded ad was displayed and user should receive the reward
            _rewardAction?.Invoke();
        }

        #endregion

        #region Ad Settings

        protected virtual string GetRewardedVideoID()
        {
            return GuruSettings.Instance.ADSetting.GetRewardedVideoID();
        }

        protected virtual string GetInterstitialID()
        {
            return GuruSettings.Instance.ADSetting.GetInterstitialID();
        }

        protected virtual string GetBannerID()
        {
            return GuruSettings.Instance.ADSetting.GetBannerID();
        }

        #endregion

        #region MaxDebugView

        public void ShowMaxDebugPanel()
        {
            if (!IsInitialized) return;
#if !UNITY_EDITOR
	        MaxSdk.ShowMediationDebugger();
#endif
        }

        #endregion

        #region DelayCall

        private void DelayCall(float time, Action callback)
        {
            CoroutineHelper.Instance.StartDelayed(time, callback);
        }

        #endregion
    }
}