using System;
using System.Threading.Tasks;
using UnityEngine;

namespace IndieExchange
{
    /// <summary>Call on Unity's main thread. No paid-ad SDK is required or called.</summary>
    public static class IndieAds
    {
        internal static IndieAdsRunner Runner;
        public static event Action<string> InterstitialOpened;
        public static event Action<string> InterstitialClosed;
        public static event Action<string, IndiePlacementStats> StatisticsChanged;
        public static bool IsInitialized => Runner != null;
        public static string ConfigurationError => Runner != null ? Runner.ConfigurationError : "Indie Ads is not initialized.";
        public static string LastLoadError => Runner != null ? Runner.LoadError : "Indie Ads is not initialized.";
        public static bool IsConfigurationReady => Runner != null && Runner.ConfigurationReady;

        public static bool Initialize(string gameId, bool testMode = false)
        {
            var settings = ScriptableObject.CreateInstance<IndieAdsSettings>();
            settings.gameId = gameId; settings.testMode = testMode;
            var result = Initialize(settings);
            UnityEngine.Object.Destroy(settings);
            return result;
        }
        public static Task<bool> TryShowInterstitialAsync() => TryShowInterstitialAsync("interstitial");
        public static Task<bool> TryShowInterstitialAsync(bool bypassPercentage) => TryShowInterstitialAsync("interstitial", bypassPercentage);
        public static bool TryShowBanner() => TryShowBanner("banner");
        public static bool TryShowBanner(bool bypassPercentage) => TryShowBanner("banner", bypassPercentage);
        public static Task<bool> TryShowRewardedAsync() => TryShowRewardedAsync("rewarded", null);
        public static Task<bool> TryShowRewardedAsync(Action onRewardEarned, bool bypassPercentage = false)
            => TryShowRewardedAsync("rewarded", onRewardEarned, bypassPercentage);
        public static bool IsReady(IndieAdFormat format) => IsReady(FormatId(format));
        public static void Preload(IndieAdFormat format) => Preload(FormatId(format));
        public static IndiePlacementStats GetStatistics(IndieAdFormat format) => GetStatistics(FormatId(format));
        private static string FormatId(IndieAdFormat format) => format == IndieAdFormat.Banner ? "banner" : format == IndieAdFormat.Interstitial ? "interstitial" : "rewarded";
        public static bool IsShowingInterstitial => Runner != null && Runner.IsShowing;
        public static bool IsShowingBanner => Runner != null && Runner.IsShowingBanner;

        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
        private static void ResetStatics()
        {
            Runner = null;
            IndieAdsRunner.Launches.Clear();
            InterstitialOpened = null;
            InterstitialClosed = null;
            StatisticsChanged = null;
        }

        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
        private static void AutoStart()
        {
            var settings = Resources.Load<IndieAdsSettings>("IndieAdsSettings");
            if (settings != null && settings.autoInitialize) Initialize(settings);
        }

        /// <summary>Call once after the host's consent/age flow permits ad requests. Starts background preloads.</summary>
        public static bool Initialize(IndieAdsSettings settings = null)
        {
            if (Runner != null) return true;
            settings = settings != null ? settings : Resources.Load<IndieAdsSettings>("IndieAdsSettings");
            if (settings == null) { Debug.LogWarning("Indie Ads: create settings through Tools > Indie Exchange > Setup."); return false; }
            var go = new GameObject("Indie Exchange");
            UnityEngine.Object.DontDestroyOnLoad(go);
            Runner = go.AddComponent<IndieAdsRunner>();
            if (Runner.Configure(settings)) return true;
            Shutdown();
            return false;
        }

        public static bool IsReady(string placement) => Runner != null && Runner.IsReady(placement);

        /// <summary>Preload without counting an opportunity. Calls are coalesced and retries are bounded.</summary>
        public static void Preload(string placement) { if (Runner != null) Runner.Preload(placement); }

        /// <summary>
        /// Counts one eligible opportunity. False completes immediately without network work.
        /// True completes after a displayed ad closes. Repeated calls while an interstitial is active
        /// share its task and do not count again or trigger a second ad. Await before resuming gameplay.
        /// Use bypassPercentage only after another provider has no fill or fails to show.
        /// Recovery uses a ready permitted category, never requests an ad, and respects a live 0% setting.
        /// </summary>
        public static Task<bool> TryShowInterstitialAsync(string placement, bool bypassPercentage = false)
        {
            try { return Runner != null ? Runner.TryShow(placement, bypassPercentage) : Task.FromResult(false); }
            catch (Exception ex) { Debug.LogWarning("Indie Ads could not show: " + ex.Message); return Task.FromResult(false); }
        }

        /// <summary>Shows a preloaded banner. Keep the host's banner hidden until this returns false.</summary>
        public static bool TryShowBanner(string placement, bool bypassPercentage = false)
        {
            try { return Runner != null && Runner.TryBanner(placement, bypassPercentage); }
            catch (Exception ex) { Debug.LogWarning("Indie Ads could not show banner: " + ex.Message); return false; }
        }

        public static void HideBanner() { if (Runner != null) Runner.HideBanner(); }

        /// <summary>True means an ad was displayed and has closed, including early exit. Grant rewards
        /// ONLY in onRewardEarned, called once after completed playback and closure. False allows provider
        /// fallback. Repeated calls during an active fullscreen ad share its task; only the first callback is used.
        /// Call only after the player accepts your clearly described reward offer.</summary>
        public static Task<bool> TryShowRewardedAsync(string placement) => TryShowRewardedAsync(placement, null);
        public static Task<bool> TryShowRewardedAsync(string placement, Action onRewardEarned, bool bypassPercentage = false)
        {
            try { return Runner != null ? Runner.TryShow(placement, bypassPercentage, IndieAdFormat.Rewarded, onRewardEarned) : Task.FromResult(false); }
            catch (Exception ex) { Debug.LogWarning("Indie Ads could not show reward: " + ex.Message); return Task.FromResult(false); }
        }

        public static IndiePlacementStats GetStatistics(string placement)
            => Runner != null ? Runner.GetStatistics(placement) : new IndiePlacementStats();

        public static void Shutdown()
        {
            if (Runner == null) return;
            var runner = Runner;
            Runner = null;
            runner.Stop();
            UnityEngine.Object.Destroy(runner.gameObject);
        }

        internal static void Opened(string placement) => Safe(() => InterstitialOpened?.Invoke(placement));
        internal static void Closed(string placement) => Safe(() => InterstitialClosed?.Invoke(placement));
        internal static void Stats(string placement, IndiePlacementStats stats) => Safe(() => StatisticsChanged?.Invoke(placement, stats.Copy()));
        private static void Safe(Action callback)
        {
            try { callback(); } catch (Exception ex) { Debug.LogException(ex); }
        }
    }
}

