using System;
using UnityEngine;

namespace Davanci.Nexus
{
    // Ads, game-agnostic. The mediation layer (AppLovin MAX) is hidden behind INexusAdsProvider;
    // this core owns the parts that don't depend on an SDK: resolving the per-platform ad unit
    // IDs from NexusConfig, the automatic analytics hook (every show forwards to
    // DavanciNexus.AdWatched / AdFailed), and a "remove ads" gate so a non-consumable IAP can
    // silence interstitials/banners while still letting rewarded ads through.
    //
    // Injectable provider so the whole flow runs deterministically in the editor against
    // FakeAdsProvider — see NexusAdsSelfTest.
    public sealed class NexusAdsCore
    {
        readonly INexusAdsProvider _provider;
        readonly NexusAdsSettings _settings;
        readonly System.Collections.Generic.List<Action<bool>> _pendingReady =
            new System.Collections.Generic.List<Action<bool>>();
        bool _ready;
        bool _initStarted;

        // True once LevelPlay/MAX Init has been dispatched; used to rebuild the core if the UI
        // polled IsRewardedReady during the ATT wait and created a stale instance.
        public bool InitStarted => _initStarted;

        public NexusAdsCore(INexusAdsProvider provider, NexusAdsSettings settings)
        {
            _provider = provider ?? new FakeAdsProvider();
            _settings = settings;
        }

        public bool IsReady => _ready;

        // Set by the game (or by IAP) to suppress interstitials + banners after a "remove ads"
        // purchase. Rewarded ads are intentionally never gated — they're an opt-in reward.
        public bool AdsRemoved { get; set; }

        // Idempotent: the provider is initialized exactly once even if several call sites ask
        // (e.g. auto-init at launch + a game/sample call). Calling the SDK's Init twice while the
        // first is still in progress can leave it stuck and never firing its ready callback.
        public void Initialize(Action<bool> onReady = null)
        {
            if (_ready) { onReady?.Invoke(true); return; }
            if (onReady != null) _pendingReady.Add(onReady);
            if (_initStarted) return;
            _initStarted = true;
            _provider.Initialize(_settings, ok =>
            {
                _ready = ok;
                if (!ok) _initStarted = false;   // allow a later retry if init failed
                var callbacks = _pendingReady.ToArray();
                _pendingReady.Clear();
                foreach (var cb in callbacks) cb?.Invoke(ok);
                NexusAds.NotifyAdsInitialized(ok);
            });
        }

        public bool IsRewardedReady => _ready && _provider.IsRewardedReady;
        public bool IsInterstitialReady => _ready && _provider.IsInterstitialReady;

        public void ShowRewarded(string placement, Action<NexusAdResult> onResult)
        {
            if (!_ready) { Done(onResult, NexusAdResult.Make(NexusAdStatus.NotInitialized, placement)); return; }
            _provider.ShowRewarded(placement, r => Done(onResult, r));
        }

        public void ShowInterstitial(string placement, Action<NexusAdResult> onResult = null)
        {
            // Suppressed by a "remove ads" purchase: no show, no analytics — just report dismissed.
            if (AdsRemoved) { onResult?.Invoke(NexusAdResult.Make(NexusAdStatus.Dismissed, placement, "ads removed")); return; }
            if (!_ready) { Done(onResult, NexusAdResult.Make(NexusAdStatus.NotInitialized, placement)); return; }
            _provider.ShowInterstitial(placement, r => Done(onResult, r));
        }

        public void ShowBanner(string placement)
        {
            if (AdsRemoved || !_ready) return;
            _provider.ShowBanner(placement);
            Report(NexusAdResult.Make(NexusAdStatus.Shown, placement));
        }

        public void HideBanner()
        {
            if (_ready) _provider.HideBanner();
        }

        public void LaunchTestSuite()
        {
            if (!_ready) Debug.LogWarning("[Nexus] Ads not initialized yet — the test suite may be empty.");
            _provider.LaunchTestSuite();
        }

        void Done(Action<NexusAdResult> cb, NexusAdResult r)
        {
            Report(r);
            cb?.Invoke(r);
        }

        // Rewarded: ad_watched only after a full watch (Status.Rewarded). Interstitial/banner: Shown.
        // Early close, no-fill, and errors → ad_failed. Matches when you grant double XP / skins.
        void Report(NexusAdResult r)
        {
            try
            {
                if (r.Status == NexusAdStatus.Rewarded || r.Status == NexusAdStatus.Shown)
                    DavanciNexus.AdWatched(r.Placement);
                else
                    DavanciNexus.AdFailed(r.Placement);
            }
            catch (Exception e) { Debug.LogWarning($"[Nexus] Ads analytics failed: {e.Message}"); }
        }
    }

    // Thin static facade in the style of NexusIAP. Routed through DavanciMonetization so games
    // call DavanciMonetization.ShowRewardedAd / ShowInterstitialAd / ShowBanner and never touch
    // this type directly.
    public static class NexusAds
    {
        static NexusAdsCore _core;
        static bool? _pendingAttTrackingAuthorized;
        static event Action<bool> _adsInitialized;
        static event Action<bool> _rewardedReadyChanged;
        static event Action<bool> _interstitialReadyChanged;
        static bool _adsInitializedFired;
        static bool _adsInitSuccess;

        // Mediation SDK finished initializing. bool = success.
        public static event Action<bool> AdsInitialized
        {
            add
            {
                _adsInitialized += value;
                if (_adsInitializedFired) value?.Invoke(_adsInitSuccess);
            }
            remove => _adsInitialized -= value;
        }

        // Fires when a rewarded ad becomes ready or is consumed (e.g. after a show).
        public static event Action<bool> RewardedReadyChanged
        {
            add
            {
                _rewardedReadyChanged += value;
                if (_core != null && _core.IsReady) value?.Invoke(IsRewardedReady);
            }
            remove => _rewardedReadyChanged -= value;
        }

        // Fires when an interstitial becomes ready or is consumed.
        public static event Action<bool> InterstitialReadyChanged
        {
            add
            {
                _interstitialReadyChanged += value;
                if (_core != null && _core.IsReady) value?.Invoke(IsInterstitialReady);
            }
            remove => _interstitialReadyChanged -= value;
        }

        internal static void NotifyAdsInitialized(bool success)
        {
            _adsInitializedFired = true;
            _adsInitSuccess = success;
            _adsInitialized?.Invoke(success);
            if (success)
            {
                NexusGameAnalyticsIlrd.Subscribe(NexusConfig.Load());
                NexusAdsRetry.StartReadinessWatch();
                // Initial ready state — late subscribers + readiness watch only see transitions otherwise.
                NotifyRewardedReadyChanged(IsRewardedReady);
                NotifyInterstitialReadyChanged(IsInterstitialReady);
            }
        }

        internal static void NotifyRewardedReadyChanged(bool ready) =>
            _rewardedReadyChanged?.Invoke(ready);

        internal static void NotifyInterstitialReadyChanged(bool ready) =>
            _interstitialReadyChanged?.Invoke(ready);

        static NexusAdsCore BuildDefault()
        {
            var cfg = NexusConfig.Load();
            var att = _pendingAttTrackingAuthorized;
            _pendingAttTrackingAuthorized = null;
            return new NexusAdsCore(SelectProvider(cfg), ResolveSettings(cfg, att));
        }

        // Create (or recreate) the core with any pending ATT state. Never call from readiness
        // polls — only from Initialize — so AttTrackingAuthorized reaches LevelPlay privacy.
        static void EnsureCoreForInit()
        {
            if (_core == null || !_core.InitStarted)
                _core = BuildDefault();
        }

        // Resolve the keys + per-platform ad unit IDs for the selected mediation. Each mediation
        // (AppLovin MAX / LevelPlay) reads its own config fields.
        static NexusAdsSettings ResolveSettings(NexusConfig cfg, bool? attTrackingAuthorized)
        {
            if (cfg == null) return default;
#if UNITY_ANDROID && !UNITY_EDITOR
            const bool android = true;
#else
            const bool android = false;
#endif
            var s = new NexusAdsSettings
            {
                TestMode = cfg.adsTestMode,
                TestSuiteMode = cfg.adsTestSuiteMode,
                AttTrackingAuthorized = attTrackingAuthorized,
            };
            if (cfg.adMediation == NexusMediation.LevelPlay)
            {
                s.AppKey = android ? cfg.androidLevelPlayAppKey : cfg.iosLevelPlayAppKey;
                s.RewardedAdUnitId = android ? cfg.androidLpRewardedAdUnitId : cfg.iosLpRewardedAdUnitId;
                s.InterstitialAdUnitId = android ? cfg.androidLpInterstitialAdUnitId : cfg.iosLpInterstitialAdUnitId;
                s.BannerAdUnitId = android ? cfg.androidLpBannerAdUnitId : cfg.iosLpBannerAdUnitId;
            }
            else
            {
                s.SdkKey = cfg.applovinSdkKey;
                s.RewardedAdUnitId = android ? cfg.androidRewardedAdUnitId : cfg.iosRewardedAdUnitId;
                s.InterstitialAdUnitId = android ? cfg.androidInterstitialAdUnitId : cfg.iosInterstitialAdUnitId;
                s.BannerAdUnitId = android ? cfg.androidBannerAdUnitId : cfg.iosBannerAdUnitId;
            }
            return s;
        }

        // Pick the live provider: the configured mediation when both SDKs are present, otherwise
        // whichever one is installed; editor and SDK-less builds use the deterministic fake.
        static INexusAdsProvider SelectProvider(NexusConfig cfg)
        {
#if UNITY_EDITOR
            return new FakeAdsProvider();
#elif DAVANCI_LEVELPLAY && DAVANCI_ADS
            return cfg != null && cfg.adMediation == NexusMediation.LevelPlay
                ? (INexusAdsProvider)new NexusLevelPlayProvider()
                : new NexusAdsProvider();
#elif DAVANCI_LEVELPLAY
            return new NexusLevelPlayProvider();
#elif DAVANCI_ADS
            return new NexusAdsProvider();
#else
            return new FakeAdsProvider();
#endif
        }

        public static bool IsReady => _core != null && _core.IsReady;
        public static bool AdsRemoved
        {
            get => _core?.AdsRemoved ?? false;
            set { if (_core != null) _core.AdsRemoved = value; }
        }

        public static void Initialize(Action<bool> onReady = null, bool? attTrackingAuthorized = null)
        {
            if (attTrackingAuthorized != null)
                _pendingAttTrackingAuthorized = attTrackingAuthorized;
            EnsureCoreForInit();
            _core.Initialize(onReady);
        }

        public static bool IsRewardedReady => _core != null && _core.IsRewardedReady;

        public static void ShowRewarded(string placement, Action<NexusAdResult> onResult)
        {
            if (_core == null) { onResult?.Invoke(NexusAdResult.Make(NexusAdStatus.NotInitialized, placement)); return; }
            _core.ShowRewarded(placement, onResult);
        }

        public static bool IsInterstitialReady => _core != null && _core.IsInterstitialReady;

        public static void ShowInterstitial(string placement, Action<NexusAdResult> onResult = null)
        {
            if (_core == null) { onResult?.Invoke(NexusAdResult.Make(NexusAdStatus.NotInitialized, placement)); return; }
            _core.ShowInterstitial(placement, onResult);
        }

        public static void ShowBanner(string placement) => _core?.ShowBanner(placement);
        public static void HideBanner() => _core?.HideBanner();
        public static void LaunchTestSuite() => _core?.LaunchTestSuite();

        public static NexusAdsCore Core => _core;

        // Lets tests / tooling swap in a fully controlled core.
        public static void OverrideCoreForTesting(NexusAdsCore core) => _core = core;
    }
}
