using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;

public enum CLDynamicLinkBehaviour
{
    Browser = 1,
    App = 2
}

[Serializable]
public class CLDynamicLinkParameters
{
    public string link;
    public string domain;
    public CLDynamicLinkBehaviour androidBehaviour = CLDynamicLinkBehaviour.App;
    public CLDynamicLinkBehaviour iosBehaviour = CLDynamicLinkBehaviour.App;

    // Optional tracking / social / misc
    public string utmSource;
    public string utmMedium;
    public string utmCampaign;
    public string utmContent;
    public string utmTerm;

    public string linkName;
    public string selectedPath;

    public string socialDescription;
    public string socialImageUrl;
    public string socialTitle;
}

[Serializable]
public class ResolvedLink
{
    public string link;
    public string shortLink;
    public string shortLinkRaw;
    public bool? isDeferred;
}

[Serializable]
public class CustomerMeta
{
    public string id;
    public string name;
    public string email;
    public string phone;
    public string emailSha256;
    public string phoneSha256;
}

[Serializable]
public class LeadMeta
{
    public string eventName;
    public Dictionary<string, object> metadata;
}

[Serializable]
public class ConversionMeta
{
    public double revenue;
    public string currency;
    public string eventName;
    public string productId;
    public string transactionId;
    public Dictionary<string, object> metadata;
}

[Serializable]
public class AttributionData
{
    public string attributionType;
    public string installId;
    public bool matchFound;
    public string matchType;
    public string shortUrl;
    public string clickedShortUrl;
    public string destinationUrl;
    public string destinationWithUtm;
    public string utmSource;
    public string utmMedium;
    public string utmCampaign;
    public string utmTerm;
    public string utmContent;
    public bool isAttributed;
}

public static class ChottuLink
{
    private const string ListenerObjectName = "___ChottuLinkListener";

#if UNITY_IOS && !UNITY_EDITOR
    [DllImport("__Internal")] private static extern void cl_bootstrap_deeplink_hook();
    [DllImport("__Internal")] private static extern void cl_init(string apiKey);
    [DllImport("__Internal")] private static extern void cl_createDynamicLink(string jsonParams);
    [DllImport("__Internal")] private static extern void cl_resolveLink(string shortUrl);
    [DllImport("__Internal")] private static extern void cl_identify(string jsonPayload);
    [DllImport("__Internal")] private static extern void cl_trackLead(string jsonPayload);
    [DllImport("__Internal")] private static extern void cl_trackConversion(string jsonPayload);
    [DllImport("__Internal")] private static extern void cl_trackEvent(string jsonPayload);
    [DllImport("__Internal")] private static extern void cl_flush();
    [DllImport("__Internal")] private static extern void cl_logout();
    [DllImport("__Internal")] private static extern void cl_optOut(bool enabled);
    [DllImport("__Internal")] private static extern void cl_isOptedOut();
    [DllImport("__Internal")] private static extern void cl_getAttributionData();
    [DllImport("__Internal")] private static extern void cl_getApiKey();
#endif

    public static event Action<string> OnLinkReceived;
    public static event Action<ResolvedLink> OnLinkReceivedWithMeta;

    private static Action<string> _pendingSuccess;
    private static Action<string> _pendingError;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    private static void OnRuntimeMethodLoad()
    {
        EnsureListener();
#if UNITY_IOS && !UNITY_EDITOR
        cl_bootstrap_deeplink_hook();
#endif
    }

    private static void EnsureListener()
    {
        if (GameObject.Find(ListenerObjectName) != null) return;
        var go = new GameObject(ListenerObjectName);
        go.AddComponent<ChottuLinkListener>();
        UnityEngine.Object.DontDestroyOnLoad(go);
    }

    // ------------------------- PUBLIC API -------------------------
    public static void Init(string apiKey)
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        using (var jc = new AndroidJavaClass("com.lib.chottu_link.ChottuLinkPlugin"))
        {
            jc.CallStatic("init", apiKey);
        }
#elif UNITY_IOS && !UNITY_EDITOR
        cl_init(apiKey);
#endif
    }

    public static void CreateDynamicLink(CLDynamicLinkParameters parameters,
                                         Action<string> onSuccess,
                                         Action<string> onError)
    {
        _pendingSuccess = onSuccess;
        _pendingError = onError;
        EnsureListener();

        var json = JsonUtility.ToJson(parameters);

#if UNITY_ANDROID && !UNITY_EDITOR
        using (var jc = new AndroidJavaClass("com.lib.chottu_link.ChottuLinkPlugin"))
        {
            jc.CallStatic("createDynamicLink", json);
        }
#elif UNITY_IOS && !UNITY_EDITOR
        cl_createDynamicLink(json);
#endif
    }

    public static void GetAppLinkDataFromUrl(string shortUrl,
                                             Action<ResolvedLink> onSuccess,
                                             Action<string> onError)
    {
        _pendingSuccess = data =>
        {
            if (onSuccess != null)
            {
                ResolvedLink resolved = null;
                try
                {
                    resolved = JsonUtility.FromJson<ResolvedLink>(data);
                }
                catch
                {
                    // ignore; we'll still try to fill from raw JSON if possible
                }

                if (resolved == null && !string.IsNullOrEmpty(data) && data.TrimStart().StartsWith("{"))
                    resolved = ParseResolvedLinkFromJsonLoose(data);

                if (resolved != null)
                    FillResolvedLinkDerivedFields(resolved, data);

                onSuccess(resolved);
            }
        };
        _pendingError = onError;
        EnsureListener();

#if UNITY_ANDROID && !UNITY_EDITOR
        using (var jc = new AndroidJavaClass("com.lib.chottu_link.ChottuLinkPlugin"))
        {
            jc.CallStatic("resolveLink", shortUrl);
        }
#elif UNITY_IOS && !UNITY_EDITOR
        cl_resolveLink(shortUrl);
#endif
    }

    public static void Identify(CustomerMeta customerMeta, Action<string> onSuccess, Action<string> onError)
    {
        if (customerMeta == null || string.IsNullOrEmpty(customerMeta.id))
        {
            onError?.Invoke("{\"code\":\"validation_error\",\"message\":\"CustomerMeta.id is required\",\"details\":\"identify\"}");
            return;
        }

        var payload = SerializeObject(new Dictionary<string, object>
        {
            {"id", customerMeta.id},
            {"name", customerMeta.name},
            {"email", customerMeta.email},
            {"phone", customerMeta.phone},
            {"emailSha256", customerMeta.emailSha256},
            {"phoneSha256", customerMeta.phoneSha256}
        });

        InvokeNative("identify", payload, onSuccess, onError);
    }

    public static void TrackLead(LeadMeta leadMeta, Action<string> onSuccess, Action<string> onError)
    {
        var payload = SerializeObject(new Dictionary<string, object>
        {
            {"eventName", leadMeta != null ? leadMeta.eventName : null},
            {"metadata", leadMeta != null ? leadMeta.metadata : null}
        });

        InvokeNative("trackLead", payload, onSuccess, onError);
    }

    public static void TrackConversion(ConversionMeta conversionMeta, Action<string> onSuccess, Action<string> onError)
    {
        if (conversionMeta == null)
        {
            onError?.Invoke("{\"code\":\"validation_error\",\"message\":\"ConversionMeta is required\",\"details\":\"trackConversion\"}");
            return;
        }

        var payload = SerializeObject(new Dictionary<string, object>
        {
            {"revenue", conversionMeta.revenue},
            {"currency", conversionMeta.currency},
            {"eventName", conversionMeta.eventName},
            {"productId", conversionMeta.productId},
            {"transactionId", conversionMeta.transactionId},
            {"metadata", conversionMeta.metadata}
        });

        InvokeNative("trackConversion", payload, onSuccess, onError);
    }

    public static void TrackEvent(string eventName, Dictionary<string, object> metadata, Action<string> onSuccess, Action<string> onError)
    {
        if (string.IsNullOrEmpty(eventName))
        {
            onError?.Invoke("{\"code\":\"validation_error\",\"message\":\"eventName is required\",\"details\":\"trackEvent\"}");
            return;
        }

        var payload = SerializeObject(new Dictionary<string, object>
        {
            {"eventName", eventName},
            {"metadata", metadata}
        });

        InvokeNative("trackEvent", payload, onSuccess, onError);
    }

    public static void Flush(Action<string> onSuccess, Action<string> onError) => InvokeNative("flush", null, onSuccess, onError);
    public static void Logout(Action<string> onSuccess, Action<string> onError) => InvokeNative("logout", null, onSuccess, onError);
    public static void OptOut(bool enabled, Action<string> onSuccess, Action<string> onError) => InvokeNative("optOut", enabled ? "true" : "false", onSuccess, onError);

    public static void IsOptedOut(Action<bool> onSuccess, Action<string> onError)
    {
        InvokeNative("isOptedOut", null, payload =>
        {
            if (bool.TryParse(payload, out var parsed))
            {
                onSuccess?.Invoke(parsed);
                return;
            }

            var parsedFromJson = ExtractNullableBool(payload, "data");
            if (parsedFromJson.HasValue)
            {
                onSuccess?.Invoke(parsedFromJson.Value);
                return;
            }

            onError?.Invoke("{\"code\":\"parse_error\",\"message\":\"Failed to parse isOptedOut response\",\"details\":\"isOptedOut\"}");
        }, onError);
    }

    public static void GetAttributionData(Action<AttributionData> onSuccess, Action<string> onError)
    {
        InvokeNative("getAttributionData", null, payload =>
        {
            AttributionData parsed = null;
            try { parsed = JsonUtility.FromJson<AttributionData>(payload); } catch { }
            if (parsed == null && !string.IsNullOrEmpty(payload) && payload.TrimStart().StartsWith("{"))
            {
                parsed = ParseAttributionDataLoose(payload);
            }

            if (parsed == null)
            {
                onError?.Invoke("{\"code\":\"parse_error\",\"message\":\"Failed to parse attribution data\",\"details\":\"getAttributionData\"}");
                return;
            }

            onSuccess?.Invoke(parsed);
        }, onError);
    }

    public static void GetApiKey(Action<string> onSuccess, Action<string> onError) => InvokeNative("getApiKey", null, onSuccess, onError);

    private static void InvokeNative(string methodName, string payload, Action<string> onSuccess, Action<string> onError)
    {
        _pendingSuccess = data =>
        {
            if (TryExtractEnvelopeData(data, out var envelopeData))
            {
                onSuccess?.Invoke(envelopeData);
                return;
            }

            onSuccess?.Invoke(data);
        };
        _pendingError = error =>
        {
            if (TryExtractEnvelopeData(error, out var envelopeError))
            {
                onError?.Invoke(envelopeError);
                return;
            }
            onError?.Invoke(error);
        };
        EnsureListener();

#if UNITY_ANDROID && !UNITY_EDITOR
        using (var jc = new AndroidJavaClass("com.lib.chottu_link.ChottuLinkPlugin"))
        {
            if (payload == null) jc.CallStatic(methodName);
            else if (methodName == "optOut")
            {
                jc.CallStatic(methodName, payload == "true");
            }
            else jc.CallStatic(methodName, payload);
        }
#elif UNITY_IOS && !UNITY_EDITOR
        switch (methodName)
        {
            case "identify": cl_identify(payload); break;
            case "trackLead": cl_trackLead(payload); break;
            case "trackConversion": cl_trackConversion(payload); break;
            case "trackEvent": cl_trackEvent(payload); break;
            case "flush": cl_flush(); break;
            case "logout": cl_logout(); break;
            case "optOut": cl_optOut(payload == "true"); break;
            case "isOptedOut": cl_isOptedOut(); break;
            case "getAttributionData": cl_getAttributionData(); break;
            case "getApiKey": cl_getApiKey(); break;
        }
#else
        onError?.Invoke("{\"code\":\"unsupported_platform\",\"message\":\"Method is only supported on Android/iOS runtime\",\"details\":\"" + methodName + "\"}");
#endif
    }

    // --------------------------------------------------------------
    private class ChottuLinkListener : MonoBehaviour
    {
        // Called from native side via UnitySendMessage
        private void OnSuccess(string payload) => _pendingSuccess?.Invoke(payload);
        private void OnError(string message)   => _pendingError?.Invoke(message);
        private void OnDeepLink(string payload)
        {
            // Native may send either plain URL, or JSON: {"link":"...","shortLink":"..."}
            if (TryParseResolvedLink(payload, out var resolved))
            {
                // Keep old event backward-compatible (send URL, not JSON)
                OnLinkReceived?.Invoke(!string.IsNullOrEmpty(resolved.link) ? resolved.link : payload);
                OnLinkReceivedWithMeta?.Invoke(resolved);
                return;
            }

            // Fallback: plain URL string
            OnLinkReceived?.Invoke(payload);
            OnLinkReceivedWithMeta?.Invoke(new ResolvedLink { link = payload, shortLink = "" });
        }

        private static bool TryParseResolvedLink(string payload, out ResolvedLink resolved)
        {
            resolved = null;
            if (string.IsNullOrEmpty(payload)) return false;

            try
            {
                var obj = JsonUtility.FromJson<ResolvedLink>(payload);
                if (obj != null && (!string.IsNullOrEmpty(obj.link) || !string.IsNullOrEmpty(obj.shortLink)))
                {
                    FillResolvedLinkDerivedFields(obj, payload);
                    resolved = obj;
                    return true;
                }
            }
            catch
            {
                // If native sent JSON but JsonUtility couldn't parse (e.g. nulls), fall back to loose parsing.
                if (payload.TrimStart().StartsWith("{"))
                {
                    var obj = ParseResolvedLinkFromJsonLoose(payload);
                    if (obj != null && (!string.IsNullOrEmpty(obj.link) || !string.IsNullOrEmpty(obj.shortLink)))
                    {
                        FillResolvedLinkDerivedFields(obj, payload);
                        resolved = obj;
                        return true;
                    }
                }
            }

            return false;
        }
    }

    private static void FillResolvedLinkDerivedFields(ResolvedLink resolved, string rawPayload)
    {
        if (resolved == null) return;
        resolved.isDeferred = ExtractNullableBool(rawPayload, "isDeferred");
        if (string.IsNullOrEmpty(resolved.shortLinkRaw))
            resolved.shortLinkRaw = resolved.shortLink;
    }

    private static ResolvedLink ParseResolvedLinkFromJsonLoose(string json)
    {
        // Very small/forgiving extractor for our known payload shape.
        // Works even if JsonUtility chokes on nulls/unsupported fields.
        var link = ExtractString(json, "link");
        var shortLink = ExtractString(json, "shortLink");
        var shortLinkRaw = ExtractString(json, "shortLinkRaw");

        if (string.IsNullOrEmpty(link) && string.IsNullOrEmpty(shortLink) && string.IsNullOrEmpty(shortLinkRaw))
            return null;

        return new ResolvedLink
        {
            link = link,
            shortLink = shortLink,
            shortLinkRaw = shortLinkRaw,
            // isDeferred is filled via FillResolvedLinkDerivedFields()
        };
    }

    private static string ExtractString(string json, string key)
    {
        if (string.IsNullOrEmpty(json) || string.IsNullOrEmpty(key)) return null;

        // Matches: "key": "value"
        var m = Regex.Match(
            json,
            $"\"{Regex.Escape(key)}\"\\s*:\\s*\"([^\"]*)\"",
            RegexOptions.IgnoreCase
        );

        return m.Success ? m.Groups[1].Value : null;
    }

    private static bool? ExtractNullableBool(string json, string key)
    {
        if (string.IsNullOrEmpty(json) || string.IsNullOrEmpty(key)) return null;

        // Matches: "key": true|false|null  (case-insensitive)
        var m = Regex.Match(
            json,
            $"\"{Regex.Escape(key)}\"\\s*:\\s*(true|false|null)",
            RegexOptions.IgnoreCase
        );

        if (!m.Success) return null;
        var v = m.Groups[1].Value.ToLowerInvariant();
        if (v == "null") return null;
        return v == "true";
    }

    private static AttributionData ParseAttributionDataLoose(string json)
    {
        return new AttributionData
        {
            attributionType = ExtractString(json, "attributionType"),
            installId = ExtractString(json, "installId"),
            matchFound = ExtractNullableBool(json, "matchFound") ?? false,
            matchType = ExtractString(json, "matchType"),
            shortUrl = ExtractString(json, "shortUrl"),
            clickedShortUrl = ExtractString(json, "clickedShortUrl"),
            destinationUrl = ExtractString(json, "destinationUrl"),
            destinationWithUtm = ExtractString(json, "destinationWithUtm"),
            utmSource = ExtractString(json, "utmSource"),
            utmMedium = ExtractString(json, "utmMedium"),
            utmCampaign = ExtractString(json, "utmCampaign"),
            utmTerm = ExtractString(json, "utmTerm"),
            utmContent = ExtractString(json, "utmContent"),
            isAttributed = ExtractNullableBool(json, "isAttributed") ?? false
        };
    }

    private static bool TryExtractEnvelopeData(string payload, out string data)
    {
        data = payload;
        if (string.IsNullOrEmpty(payload) || !payload.TrimStart().StartsWith("{")) return false;

        var matched = Regex.Match(payload, "\"data\"\\s*:\\s*(\"(?:\\\\.|[^\"])*\"|true|false|null|-?\\d+(?:\\.\\d+)?|\\{.*\\}|\\[.*\\])", RegexOptions.IgnoreCase | RegexOptions.Singleline);
        if (!matched.Success) return false;

        var raw = matched.Groups[1].Value.Trim();
        if (raw == "null")
        {
            data = "";
            return true;
        }
        if (raw.StartsWith("\"") && raw.EndsWith("\""))
        {
            data = Regex.Unescape(raw.Substring(1, raw.Length - 2));
            return true;
        }

        data = raw;
        return true;
    }

    private static string SerializeObject(object value)
    {
        return SerializeValue(value);
    }

    private static string SerializeValue(object value)
    {
        if (value == null) return "null";
        if (value is string s) return "\"" + EscapeJson(s) + "\"";
        if (value is bool b) return b ? "true" : "false";
        if (value is int || value is long || value is float || value is double || value is decimal)
            return Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture);
        if (value is IDictionary dictionary)
        {
            var sb = new StringBuilder();
            sb.Append("{");
            var first = true;
            foreach (DictionaryEntry entry in dictionary)
            {
                if (!(entry.Key is string)) continue;
                if (!first) sb.Append(",");
                sb.Append("\"").Append(EscapeJson((string)entry.Key)).Append("\":").Append(SerializeValue(entry.Value));
                first = false;
            }
            sb.Append("}");
            return sb.ToString();
        }
        if (value is IEnumerable enumerable && !(value is string))
        {
            var sb = new StringBuilder();
            sb.Append("[");
            var first = true;
            foreach (var item in enumerable)
            {
                if (!first) sb.Append(",");
                sb.Append(SerializeValue(item));
                first = false;
            }
            sb.Append("]");
            return sb.ToString();
        }
        return "\"" + EscapeJson(value.ToString()) + "\"";
    }

    private static string EscapeJson(string value)
    {
        if (string.IsNullOrEmpty(value)) return "";
        return value.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\b", "\\b").Replace("\f", "\\f").Replace("\n", "\\n").Replace("\r", "\\r").Replace("\t", "\\t");
    }
}