using System;
using System.Collections.Generic;
using System.Text;
using Runtime.ADAggregator;
using UnityEngine;

// 智能预加载实验：基于“场景出现次数/该场景实际播放请求次数”进行置信决策，
// 并允许通过策略配置进行服务端默认值覆盖。
public static class TapadnSmartLoadOrchestrator
{
    private const string StatsPrefsKey = "TapadnSmartLoadStats.v1";
    private const string DefaultScene = "__default__";
    private const string PolicyDefaultScene = "__default__";

    private static TapadnSmartLoadConfig _runtimeConfig;
    private static Dictionary<string, TapadnSmartLoadSceneState> _states = new Dictionary<string, TapadnSmartLoadSceneState>(StringComparer.Ordinal);
    private static Dictionary<string, TapadnSmartLoadPolicyItem> _policies = new Dictionary<string, TapadnSmartLoadPolicyItem>(StringComparer.Ordinal);
    private static Dictionary<string, TapadnSmartLoadCacheState> _cacheStates = new Dictionary<string, TapadnSmartLoadCacheState>(StringComparer.Ordinal);
    private static bool _initialized;
    private static bool _enabled;

    public static bool IsEnabled => _enabled;

    public static string GetDebugStateDump()
    {
        if (!_initialized)
        {
            return "{}";
        }

        var lines = new List<string>();
        foreach (var entry in _states.Values)
        {
            if (entry == null)
            {
                continue;
            }

            var adType = SafeToAdType(entry.AdType);
            if (!adType.HasValue)
            {
                continue;
            }

            var policy = ResolvePolicy(adType.Value, entry.Scenario);
            var prob = EstimateShowProbability(adType.Value, entry.Scenario);
            lines.Add(
                $"[{entry.AdType}]{entry.Scenario} e={entry.EnterCount} pReq={entry.PlayRequestCount} preReq={entry.PreloadRequestCount} preSuc={entry.PreloadSuccessCount} preFail={entry.PreloadFailureCount} showReq={entry.ShowRequestCount} showOk={entry.ShowStartCount} showFail={entry.ShowFailureCount} imm={entry.ImmediateHitCount} smartHit={entry.SmartCacheHitCount} smartUsed={entry.SmartPreloadConsumedCount} score={prob:F3} policy={policy?.PreloadThreshold:F2}");
        }

        return $"enabled={_enabled},states={lines.Count},policies={_policies.Count},cache={GetCacheDebugDump()},snapshot={string.Join(" ; ", lines)}";
    }

    public static string ExportSnapshotCsv()
    {
        if (!_initialized)
        {
            return GetSnapshotCsvHeader();
        }

        var sb = new StringBuilder();
        sb.AppendLine(GetSnapshotCsvHeader());
        foreach (var entry in _states.Values)
        {
            if (entry == null)
            {
                continue;
            }

            sb.AppendLine(string.Join(",",
                entry.AdType,
                EncodeCsv(NormalizeScenario(entry.Scenario)),
                entry.EnterCount,
                entry.PlayRequestCount,
                entry.PreloadRequestCount,
                entry.PreloadSuccessCount,
                entry.PreloadFailureCount,
                entry.ShowRequestCount,
                entry.ShowStartCount,
                entry.ShowFailureCount,
                entry.ImmediateHitCount,
                entry.SmartCacheHitCount,
                entry.SmartCacheCrossSceneHitCount,
                entry.UnattributedCacheHitCount,
                entry.SmartPreloadConsumedCount,
                entry.SmartPreloadConsumedSameSceneCount,
                entry.SmartPreloadConsumedOtherSceneCount,
                entry.SmartPreloadShowFailureCount,
                entry.SmartPreloadExpiredCount,
                EncodeCsv(NormalizeScenario(entry.LastSmartPreloadConsumedByScene)),
                entry.LastSmartPreloadConsumedUnix,
                entry.LastPreloadUnix,
                entry.LastUpdatedUnix,
                EstimateShowProbability((AD_Type)entry.AdType, entry.Scenario)));
        }

        return sb.ToString();
    }

    public static void ResetLearningState()
    {
        if (!_initialized)
        {
            return;
        }

        _states.Clear();
        _cacheStates.Clear();
        PlayerPrefs.DeleteKey(StatsPrefsKey);
        PlayerPrefs.Save();
    }

    public static void Initialize(TapadnControllerOptions options)
    {
        _runtimeConfig = BuildConfig(options);
        _enabled = options?.SmartPreloadEnabled ?? false;
        _initialized = true;
        _states = new Dictionary<string, TapadnSmartLoadSceneState>(StringComparer.Ordinal);
        _cacheStates = new Dictionary<string, TapadnSmartLoadCacheState>(StringComparer.Ordinal);
        LoadStates();
        EnsureDefaultPolicies(_runtimeConfig);
    }

    public static void OnEnterAdScenario(AD_Type adType, string scenario, string _slotId)
    {
        if (!_initialized || !_enabled)
        {
            return;
        }

        var normalizedScenario = NormalizeScenario(scenario);
        var record = GetOrCreateState(adType, normalizedScenario);
        record.EnterCount = Math.Max(0, record.EnterCount) + 1;
        record.LastUpdatedUnix = GetNowUnixSeconds();
        record.ShowRequestCount = Math.Max(0, record.ShowRequestCount);
        SaveStates();

        TryPreload(adType, normalizedScenario, _slotId);
    }

    public static void OnPlayRequestStarted(AD_Type adType, string scenario, bool cacheReadyAtRequest, string slotId = null)
    {
        if (!_initialized || !_enabled)
        {
            return;
        }

        var normalizedScenario = NormalizeScenario(scenario);
        var record = GetOrCreateState(adType, normalizedScenario);
        record.PlayRequestCount = Math.Max(0, record.PlayRequestCount) + 1;
        record.ShowRequestCount = Math.Max(0, record.ShowRequestCount) + 1;
        record.LastUpdatedUnix = GetNowUnixSeconds();
        if (cacheReadyAtRequest)
        {
            MarkImmediateHit(adType, normalizedScenario, slotId);
        }
        else
        {
            MarkCacheExpiredIfStale(adType, slotId);
        }

        SaveStates();
    }

    public static void OnLoadRequested(AD_Type adType, string scenario, string slotId = null)
    {
        if (!_initialized || !_enabled)
        {
            return;
        }

        PrepareSmartLoadRequest(adType, scenario, slotId);
    }

    public static void OnLoadStarted(AD_Type adType, string scenario, string slotId = null)
    {
        if (!_initialized || !_enabled)
        {
            return;
        }

        BeginLoadRequest(adType, scenario, slotId);
        SaveStates();
    }

    public static void OnLoadResult(AD_Type adType, string scenario, bool success, string slotId = null)
    {
        if (!_initialized || !_enabled)
        {
            return;
        }

        CompleteLoadRequest(adType, scenario, success, slotId);
        SaveStates();
    }

    public static void OnShowStart(AD_Type adType, string scenario, string slotId = null)
    {
        if (!_initialized || !_enabled)
        {
            return;
        }

        var record = GetOrCreateState(adType, NormalizeScenario(scenario));
        record.ShowStartCount = Math.Max(0, record.ShowStartCount) + 1;
        record.LastUpdatedUnix = GetNowUnixSeconds();
        MarkCacheConsumed(adType, scenario, slotId);
        SaveStates();
    }

    public static void OnShowError(AD_Type adType, string scenario, string slotId = null)
    {
        if (!_initialized || !_enabled)
        {
            return;
        }

        var record = GetOrCreateState(adType, NormalizeScenario(scenario));
        record.ShowFailureCount = Math.Max(0, record.ShowFailureCount) + 1;
        record.LastUpdatedUnix = GetNowUnixSeconds();
        MarkCacheShowFailed(adType, scenario, slotId);
        SaveStates();
    }

    private static void TryPreload(AD_Type adType, string scenario, string slotId)
    {
        if (!_initialized || !_enabled)
        {
            return;
        }

        if (!ADManager.Instance.CheckNetwork())
        {
            return;
        }

        var policy = ResolvePolicy(adType, scenario);
        if (policy == null)
        {
            return;
        }

        if (!NeedPreload(adType, scenario, policy))
        {
            return;
        }

        var now = GetNowUnixSeconds();
        var state = GetOrCreateState(adType, scenario);
        if (now - state.LastPreloadUnix < policy.CooldownSeconds)
        {
            return;
        }

        if (ADManager.Instance.IsRealy(adType))
        {
            return;
        }

        MarkCacheExpiredIfStale(adType, slotId);
        OnLoadRequested(adType, scenario, slotId);
        ADManager.Instance.LoadAD(adType);
        var smartLoadStarted = HasPendingSmartLoadForScene(adType, scenario, slotId);
        ClearPreparedLoadIfNotStarted(adType, slotId);
        if (!smartLoadStarted)
        {
            return;
        }

        state.LastPreloadUnix = now;
        state.LastUpdatedUnix = now;
        SaveStates();
        Debug.Log($"[TapADN SmartLoad] Preload triggered: type={adType}, scenario={scenario}, score={EstimateShowProbability(adType, scenario):F3}");
    }

    private static bool NeedPreload(AD_Type adType, string scenario, TapadnSmartLoadPolicyItem policy)
    {
        var score = EstimateShowProbability(adType, scenario);
        return score >= policy.PreloadThreshold;
    }

    private static float EstimateShowProbability(AD_Type adType, string scenario)
    {
        var policy = ResolvePolicy(adType, scenario);
        if (policy == null)
        {
            return 0f;
        }

        var state = GetOrCreateState(adType, scenario);
        if (state.EnterCount <= 0)
        {
            return policy.BaseProbability;
        }

        var observedRate = Mathf.Clamp01(state.PlayRequestCount / (float)state.EnterCount);
        var confidence = Mathf.Clamp01(state.EnterCount / (float)Mathf.Max(1, policy.MinSamplesForConfidence));
        var ageHours = Mathf.Max(0f, (GetNowUnixSeconds() - state.LastUpdatedUnix) / 3600f);
        var decay = Mathf.Pow(0.5f, ageHours / Mathf.Max(1f, policy.DecayHalfLifeHours));
        var trust = Mathf.Clamp01(confidence * decay);

        return Mathf.Lerp(policy.BaseProbability, observedRate, trust);
    }

    private static void PrepareSmartLoadRequest(AD_Type adType, string scenario, string slotId)
    {
        var state = GetOrCreateCacheState(adType, slotId);
        if (state.PendingSmartLoad)
        {
            return;
        }

        state.PreparedSmartLoad = true;
        state.PreparedOriginScene = NormalizeScenario(scenario);
        state.PreparedOriginScore = EstimateShowProbability(adType, scenario);
        state.PreparedRequestUnix = GetNowUnixSeconds();
    }

    private static void BeginLoadRequest(AD_Type adType, string scenario, string slotId)
    {
        var state = GetOrCreateCacheState(adType, slotId);
        var now = GetNowUnixSeconds();
        if (state.PreparedSmartLoad)
        {
            var originScene = NormalizeScenario(state.PreparedOriginScene);
            var record = GetOrCreateState(adType, originScene);
            record.PreloadRequestCount = Math.Max(0, record.PreloadRequestCount) + 1;
            record.LastUpdatedUnix = now;
            state.PendingSmartLoad = true;
            state.PendingOriginScene = originScene;
            state.PendingOriginScore = state.PreparedOriginScore;
            state.PendingRequestUnix = now;
        }
        else
        {
            state.PendingSmartLoad = false;
            state.PendingOriginScene = NormalizeScenario(scenario);
            state.PendingOriginScore = EstimateShowProbability(adType, scenario);
            state.PendingRequestUnix = now;
        }

        state.HasReadyCache = false;
        state.PreparedSmartLoad = false;
        state.PreparedOriginScene = null;
        state.PreparedOriginScore = 0f;
        state.PreparedRequestUnix = 0;
    }

    private static void ClearPreparedLoadIfNotStarted(AD_Type adType, string slotId)
    {
        var state = GetOrCreateCacheState(adType, slotId);
        if (state.PendingSmartLoad)
        {
            return;
        }

        state.PreparedSmartLoad = false;
        state.PreparedOriginScene = null;
        state.PreparedOriginScore = 0f;
        state.PreparedRequestUnix = 0;
    }

    private static bool HasPendingSmartLoadForScene(AD_Type adType, string scenario, string slotId)
    {
        var state = GetOrCreateCacheState(adType, slotId);
        return state.PendingSmartLoad &&
               string.Equals(NormalizeScenario(state.PendingOriginScene), NormalizeScenario(scenario), StringComparison.Ordinal);
    }

    private static void CompleteLoadRequest(AD_Type adType, string scenario, bool success, string slotId)
    {
        var cacheState = GetOrCreateCacheState(adType, slotId);
        var normalizedScenario = NormalizeScenario(scenario);
        var originScene = cacheState.PendingSmartLoad ? NormalizeScenario(cacheState.PendingOriginScene) : normalizedScenario;
        var originRecord = GetOrCreateState(adType, originScene);

        if (cacheState.PendingSmartLoad)
        {
            if (success)
            {
                originRecord.PreloadSuccessCount = Math.Max(0, originRecord.PreloadSuccessCount) + 1;
            }
            else
            {
                originRecord.PreloadFailureCount = Math.Max(0, originRecord.PreloadFailureCount) + 1;
            }
        }

        originRecord.LastUpdatedUnix = GetNowUnixSeconds();
        if (success)
        {
            cacheState.HasReadyCache = true;
            cacheState.ReadyFromSmart = cacheState.PendingSmartLoad;
            cacheState.ReadyOriginScene = originScene;
            cacheState.ReadyOriginScore = cacheState.PendingSmartLoad ? cacheState.PendingOriginScore : EstimateShowProbability(adType, originScene);
            cacheState.ReadyLoadedUnix = GetNowUnixSeconds();
            cacheState.ReadyRequestUnix = cacheState.PendingRequestUnix;
            cacheState.Consumed = false;
        }
        else if (cacheState.PendingSmartLoad)
        {
            ClearReadyCache(cacheState);
        }

        cacheState.PendingSmartLoad = false;
        cacheState.PendingOriginScene = null;
        cacheState.PendingOriginScore = 0f;
        cacheState.PendingRequestUnix = 0;
    }

    private static void MarkImmediateHit(AD_Type adType, string scenario, string slotId)
    {
        var consumedScene = NormalizeScenario(scenario);
        var currentSceneRecord = GetOrCreateState(adType, consumedScene);
        currentSceneRecord.ImmediateHitCount = Math.Max(0, currentSceneRecord.ImmediateHitCount) + 1;
        currentSceneRecord.LastUpdatedUnix = GetNowUnixSeconds();

        var cacheState = GetOrCreateCacheState(adType, slotId);
        if (!cacheState.HasReadyCache)
        {
            currentSceneRecord.UnattributedCacheHitCount = Math.Max(0, currentSceneRecord.UnattributedCacheHitCount) + 1;
        }
    }

    private static void MarkCacheConsumed(AD_Type adType, string scenario, string slotId)
    {
        var cacheState = GetOrCreateCacheState(adType, slotId);
        if (!cacheState.HasReadyCache || cacheState.Consumed)
        {
            return;
        }

        var consumedScene = NormalizeScenario(scenario);
        var originScene = NormalizeScenario(cacheState.ReadyOriginScene);
        var currentSceneRecord = GetOrCreateState(adType, consumedScene);
        var originSceneRecord = GetOrCreateState(adType, originScene);

        if (cacheState.ReadyFromSmart)
        {
            currentSceneRecord.SmartCacheHitCount = Math.Max(0, currentSceneRecord.SmartCacheHitCount) + 1;
            originSceneRecord.SmartPreloadConsumedCount = Math.Max(0, originSceneRecord.SmartPreloadConsumedCount) + 1;
            originSceneRecord.LastSmartPreloadConsumedByScene = consumedScene;
            originSceneRecord.LastSmartPreloadConsumedUnix = GetNowUnixSeconds();

            if (string.Equals(originScene, consumedScene, StringComparison.Ordinal))
            {
                originSceneRecord.SmartPreloadConsumedSameSceneCount = Math.Max(0, originSceneRecord.SmartPreloadConsumedSameSceneCount) + 1;
            }
            else
            {
                currentSceneRecord.SmartCacheCrossSceneHitCount = Math.Max(0, currentSceneRecord.SmartCacheCrossSceneHitCount) + 1;
                originSceneRecord.SmartPreloadConsumedOtherSceneCount = Math.Max(0, originSceneRecord.SmartPreloadConsumedOtherSceneCount) + 1;
            }
        }
        currentSceneRecord.LastUpdatedUnix = GetNowUnixSeconds();
        originSceneRecord.LastUpdatedUnix = GetNowUnixSeconds();
        cacheState.Consumed = true;
        ClearReadyCache(cacheState);
    }

    private static void MarkCacheShowFailed(AD_Type adType, string scenario, string slotId)
    {
        var cacheState = GetOrCreateCacheState(adType, slotId);
        if (!cacheState.HasReadyCache)
        {
            return;
        }

        var originScene = NormalizeScenario(cacheState.ReadyOriginScene);
        if (cacheState.ReadyFromSmart)
        {
            var originSceneRecord = GetOrCreateState(adType, originScene);
            originSceneRecord.SmartPreloadShowFailureCount = Math.Max(0, originSceneRecord.SmartPreloadShowFailureCount) + 1;
            originSceneRecord.LastSmartPreloadConsumedByScene = NormalizeScenario(scenario);
            originSceneRecord.LastSmartPreloadConsumedUnix = GetNowUnixSeconds();
            originSceneRecord.LastUpdatedUnix = GetNowUnixSeconds();
        }

        ClearReadyCache(cacheState);
    }

    private static void MarkCacheExpiredIfStale(AD_Type adType, string slotId)
    {
        var cacheState = GetOrCreateCacheState(adType, slotId);
        if (!cacheState.HasReadyCache)
        {
            return;
        }

        if (ADManager.Instance.IsRealy(adType))
        {
            return;
        }

        if (cacheState.ReadyFromSmart)
        {
            var originSceneRecord = GetOrCreateState(adType, NormalizeScenario(cacheState.ReadyOriginScene));
            originSceneRecord.SmartPreloadExpiredCount = Math.Max(0, originSceneRecord.SmartPreloadExpiredCount) + 1;
            originSceneRecord.LastUpdatedUnix = GetNowUnixSeconds();
        }

        ClearReadyCache(cacheState);
    }

    private static TapadnSmartLoadCacheState GetOrCreateCacheState(AD_Type adType, string slotId)
    {
        var key = ComposeCacheKey(adType, slotId);
        if (_cacheStates.TryGetValue(key, out var state) && state != null)
        {
            return state;
        }

        state = new TapadnSmartLoadCacheState();
        _cacheStates[key] = state;
        return state;
    }

    private static void ClearReadyCache(TapadnSmartLoadCacheState state)
    {
        if (state == null)
        {
            return;
        }

        state.HasReadyCache = false;
        state.ReadyFromSmart = false;
        state.ReadyOriginScene = null;
        state.ReadyOriginScore = 0f;
        state.ReadyLoadedUnix = 0;
        state.ReadyRequestUnix = 0;
        state.Consumed = false;
    }

    private static string GetCacheDebugDump()
    {
        if (_cacheStates == null || _cacheStates.Count == 0)
        {
            return "empty";
        }

        var lines = new List<string>();
        foreach (var entry in _cacheStates)
        {
            var state = entry.Value;
            if (state == null)
            {
                continue;
            }

            lines.Add($"[{entry.Key}]prepared={state.PreparedSmartLoad}:{NormalizeScenario(state.PreparedOriginScene)} pending={state.PendingSmartLoad}:{NormalizeScenario(state.PendingOriginScene)} ready={state.HasReadyCache}:{state.ReadyFromSmart}:{NormalizeScenario(state.ReadyOriginScene)} score={state.ReadyOriginScore:F3}");
        }

        return string.Join(" | ", lines);
    }

    private static string GetSnapshotCsvHeader()
    {
        return "ad_type,scenario,enter_count,play_request_count,preload_request,preload_success,preload_fail,show_request,show_start,show_fail,immediate_hit,smart_cache_hit,smart_cache_cross_scene_hit,unattributed_cache_hit,smart_preload_consumed,smart_preload_consumed_same_scene,smart_preload_consumed_other_scene,smart_preload_show_fail,smart_preload_expired,last_smart_preload_consumed_by_scene,last_smart_preload_consumed_unix,last_preload_unix,last_updated_unix,last_score";
    }

    private static TapadnSmartLoadPolicyItem ResolvePolicy(AD_Type adType, string scenario)
    {
        if (_runtimeConfig == null)
        {
            _runtimeConfig = BuildDefaultConfig();
        }

        var normalizedScenario = NormalizeScenario(scenario);
        var exactKey = ComposeKey(adType, normalizedScenario);
        if (_policies.TryGetValue(exactKey, out var exactPolicy))
        {
            return exactPolicy;
        }

        var defaultScenarioKey = ComposeKey(adType, PolicyDefaultScene);
        if (_policies.TryGetValue(defaultScenarioKey, out var defaultPolicy))
        {
            return defaultPolicy;
        }

        return _runtimeConfig?.GlobalDefault != null ? _runtimeConfig.GlobalDefault : BuildDefaultConfig().GlobalDefault;
    }

    private static void EnsureDefaultPolicies(TapadnSmartLoadConfig config)
    {
        _policies.Clear();
        if (config == null || config.ScenePolicies == null)
        {
            config = BuildDefaultConfig();
        }

        if (config.GlobalDefault == null)
        {
            config.GlobalDefault = BuildDefaultPolicy();
        }

        foreach (var policy in config.ScenePolicies)
        {
            if (policy == null || string.IsNullOrWhiteSpace(policy.Scene))
            {
                continue;
            }

            if (policy.AdType < 0 || policy.AdType > 2)
            {
                continue;
            }

            var key = ComposeKey((AD_Type)policy.AdType, NormalizeScenario(policy.Scene));
            _policies[key] = new TapadnSmartLoadPolicyItem
            {
                AdType = policy.AdType,
                Scene = NormalizeScenario(policy.Scene),
                BaseProbability = Mathf.Clamp01(policy.BaseProbability),
                PreloadThreshold = Mathf.Clamp(policy.PreloadThreshold, 0f, 1f),
                CooldownSeconds = Math.Max(0, policy.CooldownSeconds),
                MinSamplesForConfidence = Math.Max(1, policy.MinSamplesForConfidence),
                DecayHalfLifeHours = Math.Max(1f, policy.DecayHalfLifeHours),
            };
        }

        foreach (AD_Type adType in Enum.GetValues(typeof(AD_Type)))
        {
            var key = ComposeKey(adType, PolicyDefaultScene);
            if (!_policies.ContainsKey(key))
            {
                _policies[key] = ClonePolicy(config.GlobalDefault, adType);
            }
        }
    }

    private static TapadnSmartLoadPolicyItem ClonePolicy(TapadnSmartLoadPolicyItem source, AD_Type adType)
    {
        if (source == null)
        {
            source = BuildDefaultPolicy();
        }

        return new TapadnSmartLoadPolicyItem
        {
            AdType = (int)adType,
            Scene = PolicyDefaultScene,
            BaseProbability = source.BaseProbability,
            PreloadThreshold = source.PreloadThreshold,
            CooldownSeconds = source.CooldownSeconds,
            MinSamplesForConfidence = source.MinSamplesForConfidence,
            DecayHalfLifeHours = source.DecayHalfLifeHours
        };
    }

    private static TapadnSmartLoadConfig BuildConfig(TapadnControllerOptions options)
    {
        var config = BuildDefaultConfig();
        MergePolicyJson(config, ReadPolicyAssetJson(options));
        MergePolicyJson(config, options?.SmartPreloadConfigJson);
        MergePolicyJson(config, options?.SmartPreloadRemoteConfigJson);
        return config;
    }

    private static string ReadPolicyAssetJson(TapadnControllerOptions options)
    {
        if (options == null || string.IsNullOrWhiteSpace(options.SmartPreloadConfigAssetPath))
        {
            return null;
        }

        try
        {
            var asset = Resources.Load<TextAsset>(options.SmartPreloadConfigAssetPath);
            return asset != null ? asset.text : null;
        }
        catch (Exception exception)
        {
            Debug.LogWarning($"[TapADN SmartLoad] Load policy asset failed: {exception.Message}");
            return null;
        }
    }

    private static TapadnSmartLoadConfig BuildDefaultConfig()
    {
        return new TapadnSmartLoadConfig
        {
            GlobalDefault = BuildDefaultPolicy(),
            ScenePolicies = new List<TapadnSmartLoadPolicyItemDto>(),
        };
    }

    private static TapadnSmartLoadPolicyItem BuildDefaultPolicy()
    {
        return new TapadnSmartLoadPolicyItem
        {
            AdType = -1,
            Scene = PolicyDefaultScene,
            BaseProbability = 0.08f,
            PreloadThreshold = 0.75f,
            CooldownSeconds = 120,
            MinSamplesForConfidence = 8,
            DecayHalfLifeHours = 72f
        };
    }

    private static void MergePolicyJson(TapadnSmartLoadConfig target, string json)
    {
        if (string.IsNullOrWhiteSpace(json))
        {
            return;
        }

        try
        {
            var parsed = JsonUtility.FromJson<TapadnSmartLoadConfig>(json);
            if (parsed == null)
            {
                return;
            }

            if (parsed.GlobalDefault != null)
            {
                target.GlobalDefault = NormalizePolicy(parsed.GlobalDefault);
            }

            if (parsed.ScenePolicies == null || parsed.ScenePolicies.Count == 0)
            {
                return;
            }

            if (target.ScenePolicies == null)
            {
                target.ScenePolicies = new List<TapadnSmartLoadPolicyItemDto>();
            }

            foreach (var policy in parsed.ScenePolicies)
            {
                if (policy == null)
                {
                    continue;
                }

                if (policy.AdType < 0 || policy.AdType > 2)
                {
                    continue;
                }

                if (string.IsNullOrWhiteSpace(policy.Scene))
                {
                    continue;
                }

                target.ScenePolicies.Add(policy);
            }
        }
        catch (Exception exception)
        {
            Debug.LogWarning($"[TapADN SmartLoad] Merge policy JSON failed: {exception.Message}");
        }
    }

    private static string EncodeCsv(string value)
    {
        if (string.IsNullOrEmpty(value))
        {
            return "0";
        }

        if (value.Contains(",") || value.Contains("\"") || value.Contains("\n"))
        {
            return $"\"{value.Replace("\"", "\"\"")}\"";
        }

        return value;
    }

    private static TapadnSmartLoadPolicyItem NormalizePolicy(TapadnSmartLoadPolicyItem policy)
    {
        return new TapadnSmartLoadPolicyItem
        {
            AdType = policy.AdType,
            Scene = NormalizeScenario(policy.Scene),
            BaseProbability = Mathf.Clamp01(policy.BaseProbability),
            PreloadThreshold = Mathf.Clamp(policy.PreloadThreshold, 0f, 1f),
            CooldownSeconds = Math.Max(0, policy.CooldownSeconds),
            MinSamplesForConfidence = Math.Max(1, policy.MinSamplesForConfidence),
            DecayHalfLifeHours = Math.Max(1f, policy.DecayHalfLifeHours),
        };
    }

    private static TapadnSmartLoadSceneState GetOrCreateState(AD_Type adType, string scenario)
    {
        var key = ComposeKey(adType, NormalizeScenario(scenario));
        if (_states.TryGetValue(key, out var state))
        {
            return state;
        }

        state = new TapadnSmartLoadSceneState
        {
            AdType = (int)adType,
            Scenario = NormalizeScenario(scenario),
            EnterCount = 0,
            PlayRequestCount = 0,
            LastUpdatedUnix = GetNowUnixSeconds()
        };
        _states[key] = state;
        return state;
    }

    private static void LoadStates()
    {
        try
        {
            var raw = PlayerPrefs.GetString(StatsPrefsKey, string.Empty);
            if (string.IsNullOrWhiteSpace(raw))
            {
                return;
            }

            var data = JsonUtility.FromJson<TapadnSmartLoadStateData>(raw);
            if (data == null || data.Entries == null)
            {
                return;
            }

            foreach (var entry in data.Entries)
            {
                if (entry == null || entry.AdType < 0)
                {
                    continue;
                }

                var adType = SafeToAdType(entry.AdType);
                var scenario = NormalizeScenario(entry.Scenario);
                if (adType == null)
                {
                    continue;
                }

                var key = ComposeKey(adType.Value, scenario);
                _states[key] = entry;
            }
        }
        catch (Exception exception)
        {
            Debug.LogWarning($"[TapADN SmartLoad] Load state failed: {exception.Message}");
        }
    }

    private static AD_Type? SafeToAdType(int value)
    {
        if (value < 0 || value > 2)
        {
            return null;
        }

        return (AD_Type)value;
    }

    private static void SaveStates()
    {
        var data = new TapadnSmartLoadStateData();
        data.Entries = new List<TapadnSmartLoadSceneState>();

        foreach (var entry in _states.Values)
        {
            data.Entries.Add(entry);
        }

        var raw = JsonUtility.ToJson(data);
        if (string.IsNullOrWhiteSpace(raw))
        {
            return;
        }

        PlayerPrefs.SetString(StatsPrefsKey, raw);
        PlayerPrefs.Save();
    }

    private static string ComposeKey(AD_Type adType, string scenario)
    {
        return ((int)adType) + "|" + NormalizeScenario(scenario);
    }

    private static string ComposeCacheKey(AD_Type adType, string slotId)
    {
        return ((int)adType) + "|" + NormalizeSlotId(slotId);
    }

    private static string NormalizeSlotId(string slotId)
    {
        return string.IsNullOrWhiteSpace(slotId) ? "__default_slot__" : slotId.Trim();
    }

    private static string NormalizeScenario(string scenario)
    {
        if (string.IsNullOrWhiteSpace(scenario))
        {
            return DefaultScene;
        }

        return scenario.Trim();
    }

    private static long GetNowUnixSeconds()
    {
        return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
    }

    [Serializable]
    private sealed class TapadnSmartLoadConfig
    {
        public TapadnSmartLoadPolicyItem GlobalDefault;
        public List<TapadnSmartLoadPolicyItemDto> ScenePolicies;
    }

    [Serializable]
    private sealed class TapadnSmartLoadPolicyItem
    {
        public int AdType;
        public string Scene;
        public float BaseProbability;
        public float PreloadThreshold;
        public int CooldownSeconds;
        public int MinSamplesForConfidence;
        public float DecayHalfLifeHours;
    }

    [Serializable]
    private sealed class TapadnSmartLoadPolicyItemDto
    {
        public int AdType;
        public string Scene;
        public float BaseProbability = 0.08f;
        public float PreloadThreshold = 0.75f;
        public int CooldownSeconds = 120;
        public int MinSamplesForConfidence = 8;
        public float DecayHalfLifeHours = 72f;
    }

    [Serializable]
    private sealed class TapadnSmartLoadStateData
    {
        public int Version = 1;
        public List<TapadnSmartLoadSceneState> Entries;
    }

    private sealed class TapadnSmartLoadCacheState
    {
        public bool PreparedSmartLoad;
        public string PreparedOriginScene;
        public float PreparedOriginScore;
        public long PreparedRequestUnix;
        public bool PendingSmartLoad;
        public string PendingOriginScene;
        public float PendingOriginScore;
        public long PendingRequestUnix;
        public bool HasReadyCache;
        public bool ReadyFromSmart;
        public string ReadyOriginScene;
        public float ReadyOriginScore;
        public long ReadyLoadedUnix;
        public long ReadyRequestUnix;
        public bool Consumed;
    }

    [Serializable]
    private sealed class TapadnSmartLoadSceneState
    {
        public int AdType;
        public string Scenario;
        public int EnterCount;
        public int PlayRequestCount;
        public int PreloadRequestCount;
        public int PreloadSuccessCount;
        public int PreloadFailureCount;
        public int ShowRequestCount;
        public int ShowStartCount;
        public int ShowFailureCount;
        public int ImmediateHitCount;
        public int SmartCacheHitCount;
        public int SmartCacheCrossSceneHitCount;
        public int UnattributedCacheHitCount;
        public int SmartPreloadConsumedCount;
        public int SmartPreloadConsumedSameSceneCount;
        public int SmartPreloadConsumedOtherSceneCount;
        public int SmartPreloadShowFailureCount;
        public int SmartPreloadExpiredCount;
        public string LastSmartPreloadConsumedByScene;
        public long LastSmartPreloadConsumedUnix;
        public long LastUpdatedUnix;
        public long LastPreloadUnix;
    }
}
