using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Runtime.ADAggregator;

public sealed class ToponControllerOptions
{
    public const string ChannelKey = "topon.channel";
    public const string SubChannelKey = "topon.sub_channel";
    public const string DebugKey = "topon.debug";
    public const string DebuggerKeyKey = "topon.debugger_key";
    public const string AutoOpenDebuggerUiKey = "topon.auto_open_debugger_ui";
    public const string SDKAreaKey = "topon.sdk_area";
    public const string LongitudeKey = "topon.longitude";
    public const string LatitudeKey = "topon.latitude";
    public const string ExcludeBundleIdsKey = "topon.exclude_bundle_ids";
    public const string RewardedExcludeAdSourceIdsKey = "topon.rewarded_exclude_ad_source_ids";
    public const string QueryAreaOnInitKey = "topon.query_area_on_init";
    public const string InitCustomMapKey = "topon.custom_map";
    public const string RewardedCustomDataKey = "topon.rewarded_custom_data";
    public const string RewardedAutoLoadKey = "topon.rewarded_auto_load";
    public const string RewardedPrewarmOnInitKey = "topon.rewarded_prewarm_on_init";
    public const string RewardedMaxLoadAttemptsKey = "topon.rewarded_max_load_attempts";
    public const string RewardedLoadRetryDelayMsKey = "topon.rewarded_load_retry_delay_ms";
    public const string InterstitialAutoLoadKey = "topon.interstitial_auto_load";
    public const string InterstitialPrewarmOnInitKey = "topon.interstitial_prewarm_on_init";
    public const string InterstitialMaxLoadAttemptsKey = "topon.interstitial_max_load_attempts";
    public const string InterstitialLoadRetryDelayMsKey = "topon.interstitial_load_retry_delay_ms";
    public const string LocalStrategyAssetPathKey = "topon.local_strategy_asset_path";

    public string Channel { get; set; }
    public string SubChannel { get; set; }
    public bool? Debug { get; set; }
    public string DebuggerKey { get; set; }
    public bool AutoOpenDebuggerUI { get; set; }
    public int? SDKArea { get; set; }
    public double? Longitude { get; set; }
    public double? Latitude { get; set; }
    public string[] ExcludeBundleIds { get; set; }
    public string[] RewardedExcludeAdSourceIds { get; set; }
    public bool QueryAreaOnInit { get; set; }
    public Dictionary<string, string> InitCustomMap { get; set; }
    public Dictionary<string, string> RewardedCustomData { get; set; }
    public Action<string> OnAreaReceived { get; set; }
    public Action<string> OnAreaError { get; set; }
    public bool RewardedAutoLoad { get; set; } = true;
    public bool RewardedPrewarmOnInit { get; set; } = true;
    public int RewardedMaxLoadAttempts { get; set; } = 3;
    public int RewardedLoadRetryDelayMs { get; set; } = 1000;
    public bool InterstitialAutoLoad { get; set; } = true;
    public bool InterstitialPrewarmOnInit { get; set; } = true;
    public int InterstitialMaxLoadAttempts { get; set; } = 2;
    public int InterstitialLoadRetryDelayMs { get; set; } = 500;
    public string LocalStrategyAssetPath { get; set; }

    public static ToponControllerOptions Resolve(ADConfig adConfig, object[] args)
    {
        var options = new ToponControllerOptions();

        options.ApplyCommonKeyValues(adConfig?.CommonKeyValues);
        options.ApplyLegacyArgs(args);

        if (args == null)
        {
            return options;
        }

        foreach (var arg in args)
        {
            switch (arg)
            {
                case ToponControllerOptions explicitOptions:
                    options.ApplyExplicitOptions(explicitOptions);
                    break;
                case IDictionary dictionary:
                    options.ApplyDictionary(dictionary);
                    break;
            }
        }

        return options;
    }

    private void ApplyCommonKeyValues(List<AdKeyValue> keyValues)
    {
        if (keyValues == null || keyValues.Count == 0)
        {
            return;
        }

        var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        foreach (var keyValue in keyValues)
        {
            if (keyValue == null || string.IsNullOrWhiteSpace(keyValue.key))
            {
                continue;
            }

            map[keyValue.key] = keyValue.value;
        }

        Channel = GetString(map, ChannelKey, "channel") ?? Channel;
        SubChannel = GetString(map, SubChannelKey, "sub_channel") ?? SubChannel;
        Debug = GetBool(map, DebugKey, "debug") ?? Debug;
        DebuggerKey = GetString(map, DebuggerKeyKey) ?? DebuggerKey;
        AutoOpenDebuggerUI = GetBool(map, AutoOpenDebuggerUiKey) ?? AutoOpenDebuggerUI;
        SDKArea = GetInt(map, SDKAreaKey) ?? SDKArea;
        Longitude = GetDouble(map, LongitudeKey) ?? Longitude;
        Latitude = GetDouble(map, LatitudeKey) ?? Latitude;
        ExcludeBundleIds = GetStringArray(map, ExcludeBundleIdsKey) ?? ExcludeBundleIds;
        RewardedExcludeAdSourceIds = GetStringArray(map, RewardedExcludeAdSourceIdsKey) ?? RewardedExcludeAdSourceIds;
        QueryAreaOnInit = GetBool(map, QueryAreaOnInitKey) ?? QueryAreaOnInit;
        InitCustomMap = MergeMaps(InitCustomMap, GetPrefixedMap(map, InitCustomMapKey + "."));
        RewardedCustomData = MergeMaps(RewardedCustomData, GetPrefixedMap(map, RewardedCustomDataKey + "."));
        RewardedAutoLoad = GetBool(map, RewardedAutoLoadKey) ?? RewardedAutoLoad;
        RewardedPrewarmOnInit = GetBool(map, RewardedPrewarmOnInitKey) ?? RewardedPrewarmOnInit;
        RewardedMaxLoadAttempts = GetInt(map, RewardedMaxLoadAttemptsKey) ?? RewardedMaxLoadAttempts;
        RewardedLoadRetryDelayMs = GetInt(map, RewardedLoadRetryDelayMsKey) ?? RewardedLoadRetryDelayMs;
        InterstitialAutoLoad = GetBool(map, InterstitialAutoLoadKey) ?? InterstitialAutoLoad;
        InterstitialPrewarmOnInit = GetBool(map, InterstitialPrewarmOnInitKey) ?? InterstitialPrewarmOnInit;
        InterstitialMaxLoadAttempts = GetInt(map, InterstitialMaxLoadAttemptsKey) ?? InterstitialMaxLoadAttempts;
        InterstitialLoadRetryDelayMs = GetInt(map, InterstitialLoadRetryDelayMsKey) ?? InterstitialLoadRetryDelayMs;
        LocalStrategyAssetPath = GetString(map, LocalStrategyAssetPathKey) ?? LocalStrategyAssetPath;
    }

    private void ApplyLegacyArgs(object[] args)
    {
        if (args == null || args.Length == 0)
        {
            return;
        }

        if (args.Length > 0 && args[0] is string channel)
        {
            Channel = channel;
        }

        if (args.Length > 1 && TryConvertBool(args[1], out var debug))
        {
            Debug = debug;
        }
    }

    private void ApplyExplicitOptions(ToponControllerOptions explicitOptions)
    {
        if (explicitOptions == null)
        {
            return;
        }

        Channel = explicitOptions.Channel ?? Channel;
        SubChannel = explicitOptions.SubChannel ?? SubChannel;
        Debug = explicitOptions.Debug ?? Debug;
        DebuggerKey = explicitOptions.DebuggerKey ?? DebuggerKey;
        AutoOpenDebuggerUI = explicitOptions.AutoOpenDebuggerUI || AutoOpenDebuggerUI;
        SDKArea = explicitOptions.SDKArea ?? SDKArea;
        Longitude = explicitOptions.Longitude ?? Longitude;
        Latitude = explicitOptions.Latitude ?? Latitude;
        ExcludeBundleIds = explicitOptions.ExcludeBundleIds ?? ExcludeBundleIds;
        RewardedExcludeAdSourceIds = explicitOptions.RewardedExcludeAdSourceIds ?? RewardedExcludeAdSourceIds;
        QueryAreaOnInit = explicitOptions.QueryAreaOnInit || QueryAreaOnInit;
        InitCustomMap = MergeMaps(InitCustomMap, explicitOptions.InitCustomMap);
        RewardedCustomData = MergeMaps(RewardedCustomData, explicitOptions.RewardedCustomData);
        OnAreaReceived = explicitOptions.OnAreaReceived ?? OnAreaReceived;
        OnAreaError = explicitOptions.OnAreaError ?? OnAreaError;
    }

    private void ApplyDictionary(IDictionary dictionary)
    {
        if (dictionary == null || dictionary.Count == 0)
        {
            return;
        }

        var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        foreach (DictionaryEntry entry in dictionary)
        {
            if (entry.Key == null)
            {
                continue;
            }

            var key = entry.Key.ToString();
            if (entry.Value is IDictionary nestedDictionary)
            {
                if (string.Equals(key, InitCustomMapKey, StringComparison.OrdinalIgnoreCase))
                {
                    InitCustomMap = MergeMaps(InitCustomMap, ToStringMap(nestedDictionary));
                    continue;
                }

                if (string.Equals(key, RewardedCustomDataKey, StringComparison.OrdinalIgnoreCase))
                {
                    RewardedCustomData = MergeMaps(RewardedCustomData, ToStringMap(nestedDictionary));
                    continue;
                }
            }

            map[key] = ConvertDictionaryValue(entry.Value);
        }

        Channel = GetString(map, ChannelKey, "channel") ?? Channel;
        SubChannel = GetString(map, SubChannelKey, "sub_channel") ?? SubChannel;
        Debug = GetBool(map, DebugKey, "debug") ?? Debug;
        DebuggerKey = GetString(map, DebuggerKeyKey) ?? DebuggerKey;
        AutoOpenDebuggerUI = GetBool(map, AutoOpenDebuggerUiKey) ?? AutoOpenDebuggerUI;
        SDKArea = GetInt(map, SDKAreaKey) ?? SDKArea;
        Longitude = GetDouble(map, LongitudeKey) ?? Longitude;
        Latitude = GetDouble(map, LatitudeKey) ?? Latitude;
        ExcludeBundleIds = GetStringArray(map, ExcludeBundleIdsKey) ?? ExcludeBundleIds;
        RewardedExcludeAdSourceIds = GetStringArray(map, RewardedExcludeAdSourceIdsKey) ?? RewardedExcludeAdSourceIds;
        QueryAreaOnInit = GetBool(map, QueryAreaOnInitKey) ?? QueryAreaOnInit;
        InitCustomMap = MergeMaps(InitCustomMap, GetPrefixedMap(map, InitCustomMapKey + "."));
        RewardedCustomData = MergeMaps(RewardedCustomData, GetPrefixedMap(map, RewardedCustomDataKey + "."));
        RewardedAutoLoad = GetBool(map, RewardedAutoLoadKey) ?? RewardedAutoLoad;
        RewardedPrewarmOnInit = GetBool(map, RewardedPrewarmOnInitKey) ?? RewardedPrewarmOnInit;
        RewardedMaxLoadAttempts = GetInt(map, RewardedMaxLoadAttemptsKey) ?? RewardedMaxLoadAttempts;
        RewardedLoadRetryDelayMs = GetInt(map, RewardedLoadRetryDelayMsKey) ?? RewardedLoadRetryDelayMs;
        InterstitialAutoLoad = GetBool(map, InterstitialAutoLoadKey) ?? InterstitialAutoLoad;
        InterstitialPrewarmOnInit = GetBool(map, InterstitialPrewarmOnInitKey) ?? InterstitialPrewarmOnInit;
        InterstitialMaxLoadAttempts = GetInt(map, InterstitialMaxLoadAttemptsKey) ?? InterstitialMaxLoadAttempts;
        InterstitialLoadRetryDelayMs = GetInt(map, InterstitialLoadRetryDelayMsKey) ?? InterstitialLoadRetryDelayMs;
        LocalStrategyAssetPath = GetString(map, LocalStrategyAssetPathKey) ?? LocalStrategyAssetPath;
    }

    private static string GetString(IDictionary<string, string> map, params string[] keys)
    {
        foreach (var key in keys)
        {
            if (!string.IsNullOrWhiteSpace(key) &&
                map.TryGetValue(key, out var value) &&
                !string.IsNullOrWhiteSpace(value))
            {
                return value.Trim();
            }
        }

        return null;
    }

    private static bool? GetBool(IDictionary<string, string> map, params string[] keys)
    {
        var value = GetString(map, keys);
        return TryConvertBool(value, out var result) ? result : null;
    }

    private static int? GetInt(IDictionary<string, string> map, params string[] keys)
    {
        var value = GetString(map, keys);
        if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
        {
            return result;
        }

        return null;
    }

    private static double? GetDouble(IDictionary<string, string> map, params string[] keys)
    {
        var value = GetString(map, keys);
        if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture,
                out var result))
        {
            return result;
        }

        return null;
    }

    private static string[] GetStringArray(IDictionary<string, string> map, params string[] keys)
    {
        var value = GetString(map, keys);
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        var normalized = value.Replace("[", string.Empty)
            .Replace("]", string.Empty)
            .Replace("\"", string.Empty)
            .Replace("'", string.Empty);

        var items = normalized
            .Split(new[] { ',', ';', '|', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
            .Select(item => item.Trim())
            .Where(item => !string.IsNullOrWhiteSpace(item))
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToArray();

        return items.Length > 0 ? items : null;
    }

    private static Dictionary<string, string> GetPrefixedMap(IDictionary<string, string> map, string prefix)
    {
        var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        foreach (var pair in map)
        {
            if (!pair.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            var nestedKey = pair.Key.Substring(prefix.Length).Trim();
            if (string.IsNullOrWhiteSpace(nestedKey) || string.IsNullOrWhiteSpace(pair.Value))
            {
                continue;
            }

            result[nestedKey] = pair.Value.Trim();
        }

        return result.Count > 0 ? result : null;
    }

    private static Dictionary<string, string> MergeMaps(
        Dictionary<string, string> current,
        Dictionary<string, string> incoming)
    {
        if (incoming == null || incoming.Count == 0)
        {
            return current;
        }

        var result = current != null
            ? new Dictionary<string, string>(current, StringComparer.OrdinalIgnoreCase)
            : new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

        foreach (var pair in incoming)
        {
            if (string.IsNullOrWhiteSpace(pair.Key) || string.IsNullOrWhiteSpace(pair.Value))
            {
                continue;
            }

            result[pair.Key] = pair.Value;
        }

        return result.Count > 0 ? result : null;
    }

    private static bool TryConvertBool(object value, out bool result)
    {
        switch (value)
        {
            case bool boolValue:
                result = boolValue;
                return true;
            case string stringValue:
                if (bool.TryParse(stringValue, out result))
                {
                    return true;
                }

                if (string.Equals(stringValue, "1", StringComparison.OrdinalIgnoreCase))
                {
                    result = true;
                    return true;
                }

                if (string.Equals(stringValue, "0", StringComparison.OrdinalIgnoreCase))
                {
                    result = false;
                    return true;
                }

                break;
        }

        result = false;
        return false;
    }

    private static string ConvertDictionaryValue(object value)
    {
        if (value == null)
        {
            return null;
        }

        if (value is string stringValue)
        {
            return stringValue;
        }

        if (value is IEnumerable enumerable)
        {
            var items = new List<string>();
            foreach (var item in enumerable)
            {
                if (item == null)
                {
                    continue;
                }

                items.Add(item.ToString());
            }

            return string.Join(",", items);
        }

        return value.ToString();
    }

    private static Dictionary<string, string> ToStringMap(IDictionary dictionary)
    {
        var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        foreach (DictionaryEntry entry in dictionary)
        {
            if (entry.Key == null || entry.Value == null)
            {
                continue;
            }

            var key = entry.Key.ToString()?.Trim();
            var value = entry.Value.ToString()?.Trim();
            if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(value))
            {
                continue;
            }

            result[key] = value;
        }

        return result.Count > 0 ? result : null;
    }
}
