using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using AppLovinMax.ThirdParty.MiniJson;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class MaxSdkUtils
{
    /// 
    /// An Enum to be used when comparing two versions.
    ///
    /// If:
    ///     A < B    return  
    public enum VersionComparisonResult
    {
        Lesser = -1,
        Equal = 0,
        Greater = 1
    }
#if UNITY_ANDROID && !UNITY_EDITOR
    private static readonly AndroidJavaClass MaxUnityPluginClass = new AndroidJavaClass("com.applovin.mediation.unity.MaxUnityPlugin");
#endif
#if UNITY_IOS
    [DllImport("__Internal")]
    private static extern float _MaxGetAdaptiveBannerHeight(float width);
#endif
    /// 
    /// Get the adaptive banner size for the provided width.
    /// If the width is not provided, will assume full screen width for the current orientation.
    ///
    /// NOTE: Only AdMob / Google Ad Manager currently has support for adaptive banners and the maximum height is 15% the height of the screen.
    ///  
    ///
    /// The adaptive banner height for the current orientation and width. 
    public static float GetAdaptiveBannerHeight(float width = -1.0f)
    {
#if UNITY_EDITOR
        return 50.0f;
#elif UNITY_IOS
        return _MaxGetAdaptiveBannerHeight(width);
#elif UNITY_ANDROID
        return MaxUnityPluginClass.CallStatic("getAdaptiveBannerHeight", width);
#else
        return -1.0f;
#endif
    }
    /// 
    /// Tries to get a dictionary for the given key if available, returns the default value if unavailable.
    ///  
    /// The dictionary for the given key if available, the default value otherwise. 
    public static Dictionary GetDictionaryFromDictionary(IDictionary dictionary, string key, Dictionary defaultValue = null)
    {
        if (dictionary == null) return defaultValue;
        object value;
        if (dictionary.TryGetValue(key, out value) && value is Dictionary)
        {
            return value as Dictionary;
        }
        return defaultValue;
    }
    /// 
    /// Tries to get a list from the dictionary for the given key if available, returns the default value if unavailable.
    ///  
    /// The list for the given key if available, the default value otherwise. 
    public static List GetListFromDictionary(IDictionary dictionary, string key, List defaultValue = null)
    {
        if (dictionary == null) return defaultValue;
        object value;
        if (dictionary.TryGetValue(key, out value) && value is List)
        {
            return value as List;
        }
        return defaultValue;
    }
    /// 
    /// Tries to get a string  value from dictionary for the given key if available, returns the default value if unavailable.  
    ///  
    /// string  value.
    /// string  value.
    /// The string  value from the dictionary if available, the default value otherwise. 
    public static string GetStringFromDictionary(IDictionary dictionary, string key, string defaultValue = "")
    {
        if (dictionary == null) return defaultValue;
        object value;
        if (dictionary.TryGetValue(key, out value) && value != null)
        {
            return value.ToString();
        }
        return defaultValue;
    }
    /// 
    /// Tries to get a bool  value from dictionary for the given key if available, returns the default value if unavailable.
    ///  
    /// bool  value.
    /// bool  value.
    /// bool  value for the given key is not found.
    /// The bool  value from the dictionary if available, the default value otherwise. 
    public static bool GetBoolFromDictionary(IDictionary dictionary, string key, bool defaultValue = false)
    {
        if (dictionary == null) return defaultValue;
        object obj;
        bool value;
        if (dictionary.TryGetValue(key, out obj) && obj != null && bool.TryParse(obj.ToString(), out value))
        {
            return value;
        }
        return defaultValue;
    }
    /// 
    /// Tries to get a int  value from dictionary for the given key if available, returns the default value if unavailable.
    ///  
    /// int  value.
    /// int  value.
    /// int  value for the given key is not found.
    /// The int  value from the dictionary if available, the default value otherwise. 
    public static int GetIntFromDictionary(IDictionary dictionary, string key, int defaultValue = 0)
    {
        if (dictionary == null) return defaultValue;
        object obj;
        int value;
        if (dictionary.TryGetValue(key, out obj) &&
            obj != null &&
            int.TryParse(InvariantCultureToString(obj), NumberStyles.Any, CultureInfo.InvariantCulture, out value))
        {
            return value;
        }
        return defaultValue;
    }
    /// 
    /// Tries to get a long  value from dictionary for the given key if available, returns the default value if unavailable.
    ///  
    /// long  value.
    /// long  value.
    /// long  value for the given key is not found.
    /// The long  value from the dictionary if available, the default value otherwise. 
    public static long GetLongFromDictionary(IDictionary dictionary, string key, long defaultValue = 0L)
    {
        if (dictionary == null) return defaultValue;
        object obj;
        long value;
        if (dictionary.TryGetValue(key, out obj) &&
            obj != null &&
            long.TryParse(InvariantCultureToString(obj), NumberStyles.Any, CultureInfo.InvariantCulture, out value))
        {
            return value;
        }
        return defaultValue;
    }
    /// 
    /// Tries to get a float  value from dictionary for the given key if available, returns the default value if unavailable.
    ///  
    /// float  value.
    /// float  value.
    /// string  value for the given key is not found.
    /// The float  value from the dictionary if available, the default value otherwise. 
    public static float GetFloatFromDictionary(IDictionary dictionary, string key, float defaultValue = 0F)
    {
        if (dictionary == null) return defaultValue;
        object obj;
        float value;
        if (dictionary.TryGetValue(key, out obj) &&
            obj != null &&
            float.TryParse(InvariantCultureToString(obj), NumberStyles.Any, CultureInfo.InvariantCulture, out value))
        {
            return value;
        }
        return defaultValue;
    }
    /// 
    /// Tries to get a double  value from dictionary for the given key if available, returns the default value if unavailable.
    ///  
    /// double  value.
    /// double  value.
    /// double  value for the given key is not found.
    /// The double  value from the dictionary if available, the default value otherwise. 
    public static double GetDoubleFromDictionary(IDictionary dictionary, string key, int defaultValue = 0)
    {
        if (dictionary == null) return defaultValue;
        object obj;
        double value;
        if (dictionary.TryGetValue(key, out obj) &&
            obj != null &&
            double.TryParse(InvariantCultureToString(obj), NumberStyles.Any, CultureInfo.InvariantCulture, out value))
        {
            return value;
        }
        return defaultValue;
    }
    /// 
    /// Converts the given object to a string without locale specific conversions.
    ///  
    public static string InvariantCultureToString(object obj)
    {
        return string.Format(CultureInfo.InvariantCulture, "{0}", obj);
    }
    /// 
    /// The native iOS and Android plugins forward JSON arrays of JSON Objects.
    ///  
    public static List PropsStringsToList(string str)
    {
        var result = new List();
        if (string.IsNullOrEmpty(str)) return result;
        var infoArray = Json.Deserialize(str) as List;
        if (infoArray == null) return result;
        foreach (var infoObject in infoArray)
        {
            var dictionary = infoObject as Dictionary;
            if (dictionary == null) continue;
            // Dynamically construct generic type with string argument.
            // The type T must have a constructor that creates a new object from an info string, i.e., new T(infoString)
            var instance = (T) Activator.CreateInstance(typeof(T), dictionary);
            result.Add(instance);
        }
        return result;
    }
    /// 
    /// Returns the hexidecimal color code string for the given Color.
    ///  
    public static String ParseColor(Color color)
    {
        int a = (int) (Mathf.Clamp01(color.a) * Byte.MaxValue);
        int r = (int) (Mathf.Clamp01(color.r) * Byte.MaxValue);
        int g = (int) (Mathf.Clamp01(color.g) * Byte.MaxValue);
        int b = (int) (Mathf.Clamp01(color.b) * Byte.MaxValue);
        return BitConverter.ToString(new[]
        {
            Convert.ToByte(a),
            Convert.ToByte(r),
            Convert.ToByte(g),
            Convert.ToByte(b),
        }).Replace("-", "").Insert(0, "#");
    }
#if UNITY_IOS
    [DllImport("__Internal")]
    private static extern bool _MaxIsTablet();
#endif
    /// 
    /// Returns whether or not the device is a tablet.
    ///  
    public static bool IsTablet()
    {
#if UNITY_EDITOR
        return false;
#elif UNITY_IOS
        return _MaxIsTablet();
#elif UNITY_ANDROID
        return MaxUnityPluginClass.CallStatic("isTablet");
#else
        return false;
#endif
    }
#if UNITY_IOS
    [DllImport("__Internal")]
    private static extern bool _MaxIsPhysicalDevice();
#endif
    /// 
    /// Returns whether or not a physical device is being used, as opposed to an emulator / simulator.
    ///  
    public static bool IsPhysicalDevice()
    {
#if UNITY_EDITOR
        return false;
#elif UNITY_IOS
        return _MaxIsPhysicalDevice();
#elif UNITY_ANDROID
        return MaxUnityPluginClass.CallStatic("isPhysicalDevice");
#else
        return false;
#endif
    }
#if UNITY_IOS
    [DllImport("__Internal")]
    private static extern float _MaxScreenDensity();
#endif
    /// 
    /// Returns the screen density.
    ///  
    public static float GetScreenDensity()
    {
#if UNITY_EDITOR
        return 1;
#elif UNITY_IOS
        return _MaxScreenDensity();
#elif UNITY_ANDROID
        return MaxUnityPluginClass.CallStatic("getScreenDensity");
#else
        return -1;
#endif
    }
    /// 
    /// Parses the IABTCF_VendorConsents string to determine the consent status of the IAB vendor with the provided ID.
    /// NOTE: Must be called after AppLovin MAX SDK has been initialized.
    ///  
    /// true  if the vendor has consent, false  if not, or null  if TC data is not available on disk.Current Version of Global Vendor List 
    public static bool? GetTcfConsentStatus(int vendorId)
    {
        var tcfConsentStatus = GetPlatformSpecificTcfConsentStatus(vendorId);
        return GetConsentStatusValue(tcfConsentStatus);
    }
#if UNITY_IOS
    [DllImport("__Internal")]
    private static extern int _MaxGetTcfVendorConsentStatus(int vendorIdentifier);
#endif
    private static int GetPlatformSpecificTcfConsentStatus(int vendorId)
    {
#if UNITY_EDITOR
        return -1;
#elif UNITY_IOS
        return _MaxGetTcfVendorConsentStatus(vendorId);
#elif UNITY_ANDROID
        return MaxUnityPluginClass.CallStatic("getTcfVendorConsentStatus", vendorId);
#else
        return -1;
#endif
    }
    /// 
    /// Parses the IABTCF_AddtlConsent string to determine the consent status of the advertising entity with the provided Ad Technology Provider (ATP) ID.
    /// NOTE: Must be called after AppLovin MAX SDK has been initialized.
    ///  
    /// 
    /// true  if the advertising entity has consent, false  if not, or null  if no AC string is available on disk or the ATP network was not listed in the CMP flow.
    ///  
    /// Google’s Additional Consent Mode technical specification 
    /// List of Google ATPs and their IDs 
    public static bool? GetAdditionalConsentStatus(int atpId)
    {
        var additionalConsentStatus = GetPlatformSpecificAdditionalConsentStatus(atpId);
        return GetConsentStatusValue(additionalConsentStatus);
    }
#if UNITY_IOS
    [DllImport("__Internal")]
    private static extern int _MaxGetAdditionalConsentStatus(int atpIdentifier);
#endif
    private static int GetPlatformSpecificAdditionalConsentStatus(int atpId)
    {
#if UNITY_EDITOR
        return -1;
#elif UNITY_IOS
        return _MaxGetAdditionalConsentStatus(atpId);
#elif UNITY_ANDROID
        return MaxUnityPluginClass.CallStatic("getAdditionalConsentStatus", atpId);
#else
        return -1;
#endif
    }
    /// 
    /// Parses the IABTCF_PurposeConsents String to determine the consent status of the IAB defined data processing purpose.
    /// NOTE: Must be called after AppLovin MAX SDK has been initialized.
    ///  
    /// true  if the purpose has consent, false  if not, or null  if TC data is not available on disk.see IAB Europe Transparency and Consent Framework Policies (Appendix A) for purpose definitions. 
    public static bool? GetPurposeConsentStatus(int purposeId)
    {
        var purposeConsentStatus = GetPlatformSpecificPurposeConsentStatus(purposeId);
        return GetConsentStatusValue(purposeConsentStatus);
    }
#if UNITY_IOS
    [DllImport("__Internal")]
    private static extern int _MaxGetPurposeConsentStatus(int purposeIdentifier);
#endif
    private static int GetPlatformSpecificPurposeConsentStatus(int purposeId)
    {
#if UNITY_EDITOR
        return -1;
#elif UNITY_IOS
        return _MaxGetPurposeConsentStatus(purposeId);
#elif UNITY_ANDROID
        return MaxUnityPluginClass.CallStatic("getPurposeConsentStatus", purposeId);
#else
        return -1;
#endif
    }
    /// 
    /// Parses the IABTCF_SpecialFeaturesOptIns String to determine the opt-in status of the IAB defined special feature.
    /// NOTE: Must be called after AppLovin MAX SDK has been initialized.
    ///  
    /// true  if the user opted in for the special feature, false  if not, or null  if TC data is not available on disk.IAB Europe Transparency and Consent Framework Policies (Appendix A) for special features  
    public static bool? GetSpecialFeatureOptInStatus(int specialFeatureId)
    {
        var specialFeatureOptInStatus = GetPlatformSpecificSpecialFeatureOptInStatus(specialFeatureId);
        return GetConsentStatusValue(specialFeatureOptInStatus);
    }
#if UNITY_IOS
    [DllImport("__Internal")]
    private static extern int _MaxGetSpecialFeatureOptInStatus(int specialFeatureIdentifier);
#endif
    private static int GetPlatformSpecificSpecialFeatureOptInStatus(int specialFeatureId)
    {
#if UNITY_EDITOR
        return -1;
#elif UNITY_IOS
        return _MaxGetSpecialFeatureOptInStatus(specialFeatureId);
#elif UNITY_ANDROID
        return MaxUnityPluginClass.CallStatic("getSpecialFeatureOptInStatus", specialFeatureId);
#else
        return -1;
#endif
    }
    private static bool? GetConsentStatusValue(int consentStatus)
    {
        if (consentStatus == -1)
        {
            return null;
        }
        else
        {
            return consentStatus == 1;
        }
    }
    /// 
    /// Compares its two arguments for order.  Returns  
    /// 
    ///  
    public static VersionComparisonResult CompareVersions(string versionA, string versionB)
    {
        if (versionA.Equals(versionB)) return VersionComparisonResult.Equal;
        // Check if either of the versions are beta versions. Beta versions could be of format x.y.z-beta or x.y.z-betaX.
        // Split the version string into beta component and the underlying version.
        int piece;
        var isVersionABeta = versionA.Contains("-beta");
        var versionABetaNumber = 0;
        if (isVersionABeta)
        {
            var components = versionA.Split(new[] {"-beta"}, StringSplitOptions.None);
            versionA = components[0];
            versionABetaNumber = int.TryParse(components[1], out piece) ? piece : 0;
        }
        var isVersionBBeta = versionB.Contains("-beta");
        var versionBBetaNumber = 0;
        if (isVersionBBeta)
        {
            var components = versionB.Split(new[] {"-beta"}, StringSplitOptions.None);
            versionB = components[0];
            versionBBetaNumber = int.TryParse(components[1], out piece) ? piece : 0;
        }
        // Now that we have separated the beta component, check if the underlying versions are the same.
        if (versionA.Equals(versionB))
        {
            // The versions are the same, compare the beta components.
            if (isVersionABeta && isVersionBBeta)
            {
                if (versionABetaNumber < versionBBetaNumber) return VersionComparisonResult.Lesser;
                if (versionABetaNumber > versionBBetaNumber) return VersionComparisonResult.Greater;
            }
            // Only VersionA is beta, so A is older.
            else if (isVersionABeta)
            {
                return VersionComparisonResult.Lesser;
            }
            // Only VersionB is beta, A is newer.
            else
            {
                return VersionComparisonResult.Greater;
            }
        }
        // Compare the non beta component of the version string.
        var versionAComponents = versionA.Split('.').Select(version => int.TryParse(version, out piece) ? piece : 0).ToArray();
        var versionBComponents = versionB.Split('.').Select(version => int.TryParse(version, out piece) ? piece : 0).ToArray();
        var length = Mathf.Max(versionAComponents.Length, versionBComponents.Length);
        for (var i = 0; i < length; i++)
        {
            var aComponent = i < versionAComponents.Length ? versionAComponents[i] : 0;
            var bComponent = i < versionBComponents.Length ? versionBComponents[i] : 0;
            if (aComponent < bComponent) return VersionComparisonResult.Lesser;
            if (aComponent > bComponent) return VersionComparisonResult.Greater;
        }
        return VersionComparisonResult.Equal;
    }
    /// 
    /// Check if the given string is valid - not null  and not empty.
    ///  
    /// true  if the given string is not null  and not empty.
    /// Gets the path of the asset in the project for a given MAX plugin export path.
    ///  
    /// The exported path of the MAX plugin asset or the default export path if the asset is not found. 
    public static string GetAssetPathForExportPath(string exportPath)
    {
        var assetLabelToFind = "al_max_export_path-" + exportPath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
        var labelSearchQuery = "l:" + assetLabelToFind;
        var assetGuids = AssetDatabase.FindAssets(labelSearchQuery);
        // Search all assets returned from the label query (may include partial matches)
        foreach (var guid in assetGuids)
        {
            var path = AssetDatabase.GUIDToAssetPath(guid);
            var asset = AssetDatabase.LoadMainAssetAtPath(path);
            if (asset == null) continue;
            var labels = AssetDatabase.GetLabels(asset);
            // Check if any label exactly matches
            if (labels.Any(label => label == assetLabelToFind)) return path;
        }
        // Fall back to the default path if no exact label match is found
        return Path.Combine("Assets", exportPath);
    }
#endif
}