using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Alibabacloud.Rum.Unity;
using Alibabacloud.Rum.Unity.Logs;
using Alibabacloud.Rum.Unity.Rum;

// =============================================================================
// KNOWN ISSUE: Android JNI Threading Problem
// =============================================================================
// InstrumentedHttpMessageHandler.SendAsync is an async Task method. The finally 
// block calls rum.ReportResource(...) which leads to:
//   AndroidRum.ReportResource() 
//   → traceContext.ToJavaObject() 
//   → protocolClass.CallStatic<AndroidJavaObject>("valueOf", enumName)
//
// This call chain executes on a .NET thread pool background thread, NOT the 
// Unity main thread. Unity's AndroidJavaObject requires calls to be made on 
// the main thread (or a JVM-attached thread).
//
// When JNI methods are called from a background thread:
// - Unity internally fails silently or returns invalid objects
// - CallStatic<AndroidJavaObject> does NOT throw an exception
// - The returned AndroidJavaObject's internal JNI pointer is invalid
// - C# side evaluates the object as null
//
// TODO: Implement main thread dispatch mechanism for ReportResource calls.
// Potential solutions:
// 1. Use UnityMainThreadDispatcher to marshal calls to main thread
// 2. Cache report data and process in Update() on a MonoBehaviour
// 3. Use AndroidJNI.AttachCurrentThread() before JNI calls (requires careful management)
// =============================================================================

namespace Alibabacloud.Rum.Unity.Network
{
    /// <summary>
    /// This class is for internal use only and should not be used directly.
    /// </summary>
    [Obsolete("This class is for internal use only and will be removed in a future version.")]
    internal class InstrumentedHttpMessageHandler : DelegatingHandler
    {
        private readonly AlibabacloudOptions _options;

        internal InstrumentedHttpMessageHandler(HttpMessageHandler innerHandler, AlibabacloudOptions options)
            : base(innerHandler)
        {
            _options = options;
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var sdk = AlibabacloudSdk.Instance;
            var options = _options ?? sdk.Options;
        
            if (options == null || !options.EnableNetworkTracking || request == null || request.RequestUri == null)
            {
                return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
            }
        
            var url = request.RequestUri.ToString();
            if (!NetworkInstrumentationFacadeHttp.ShouldTrackRequest(options, url, request.Method.Method))
            {
                return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
            }
        
            // Prepare trace context before request
            TraceContext traceContext = null;
            TraceContextConfig traceConfig = null;
            if (options.EnableTraceContext)
            {          
                traceConfig = TraceContextManager.GetMatchingConfig(options, url);
                if (traceConfig != null)
                {
                    traceContext = new TraceContext(
                        TraceContextManager.GetTraceId(),
                        TraceContextManager.GenerateNewSpanId(),
                        TraceContextStandard.SkyWalking); // traceConfig.Standard
                    
                    // Inject trace context headers
                    var traceHeaders = TraceContextManager.GetTraceHeaders(options, url);
                    foreach (var header in traceHeaders)
                    {
                        request.Headers.TryAddWithoutValidation(header.Key, header.Value);
                    }
                }
            }

            var key = NetworkInstrumentationFacadeHttp.GenerateResourceKey(url);
            var rum = sdk.Rum;
        
            var attributes = NetworkInstrumentationFacadeHttp.BuildStartAttributes(options, request);
            var httpMethod = MapHttpMethod(request.Method.Method);
        
            // Start timing
            var stopwatch = Stopwatch.StartNew();
            HttpResponseMessage response = null;
            long? responseSize = null;
            int statusCode = 0;
            string errorMessage = null;
            bool success = false;
                    
            try
            {
                response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
                statusCode = (int)response.StatusCode;
                success = response.IsSuccessStatusCode;
        
                // Status code range filter
                if (!NetworkInstrumentationFacadeHttp.ShouldTrackStatusCode(options, statusCode))
                {
                    return response;
                }
        
                // Add response headers if configured
                if (options.CaptureResponseHeaders && response.Headers != null)
                {
                    var responseHeaders = NetworkInstrumentationFacadeHttp.CaptureHeaders(
                        response.Headers,
                        options.NetworkResponseHeaderAllowlist,
                        options.NetworkResponseHeaderBlocklist);
                    foreach (var header in responseHeaders)
                    {
                        attributes[$"network.response_header.{header.Key}"] = string.Join(", ", header.Value);
                    }
                }
        
                // Capture response body size
                if (options.CaptureResponseBodySize && response.Content != null)
                {
                    var contentBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
                    responseSize = contentBytes?.LongLength ?? 0L;
                }
        
            }
            catch (Exception e)
            {
                success = false;
                errorMessage = e.Message;
                        
                var errorInfo = new ErrorInfo(e);
                
                // todo delete
            }
            finally
            {
                stopwatch.Stop();
                        
                // Report resource with detailed metrics
                try
                {
                    var measuring = new Measuring(
                        duration: stopwatch.ElapsedMilliseconds,
                        size: responseSize ?? 0L);
                    rum.ReportResource(
                        type: "xhr",
                        url: url,
                        method: request.Method.Method.ToUpperInvariant(),
                        statusCode: statusCode,
                        errorMessage: errorMessage ?? string.Empty,
                        success: success,
                        traceContext: traceContext,
                        measuring: measuring);
                }
                catch (Exception ex)
                {
                    options.LogError(ex, "Failed to report resource: {0}", ex.Message);
                }
            }
        
            if (response == null)
            {
                throw new HttpRequestException(errorMessage ?? "Request failed");
            }
                    
            return response;
        }

        private static RumHttpMethod MapHttpMethod(string method)
        {
            if (string.IsNullOrEmpty(method))
            {
                return RumHttpMethod.Get;
            }

            switch (method.ToUpperInvariant())
            {
                case "POST":
                    return RumHttpMethod.Post;
                case "HEAD":
                    return RumHttpMethod.Head;
                case "PUT":
                    return RumHttpMethod.Put;
                case "DELETE":
                    return RumHttpMethod.Delete;
                case "PATCH":
                    return RumHttpMethod.Patch;
                default:
                    return RumHttpMethod.Get;
            }
        }
    }

    internal static class NetworkInstrumentationFacadeHttp
    {
        public static bool ShouldTrackRequest(AlibabacloudOptions options, string url, string method = null)
        {
            if (!options.EnableNetworkTracking || string.IsNullOrEmpty(url))
            {
                return false;
            }

            // HTTP Method filter
            if (!string.IsNullOrEmpty(method) && HasHttpMethodFilters(options))
            {
                if (!IsHttpMethodAllowed(options, method))
                {
                    return false;
                }
            }

            // URL Blocklist
            if (options.NetworkUrlBlocklist != null)
            {
                foreach (var pattern in options.NetworkUrlBlocklist)
                {
                    if (!string.IsNullOrEmpty(pattern) && url.IndexOf(pattern, StringComparison.Ordinal) >= 0)
                    {
                        return false;
                    }
                }
            }

            // URL Allowlist
            var allowlist = options.NetworkUrlAllowlist;
            if (allowlist != null && allowlist.Length > 0)
            {
                var matchedAllowlist = false;
                foreach (var pattern in allowlist)
                {
                    if (!string.IsNullOrEmpty(pattern) && url.IndexOf(pattern, StringComparison.Ordinal) >= 0)
                    {
                        matchedAllowlist = true;
                        break;
                    }
                }

                if (!matchedAllowlist)
                {
                    return false;
                }
            }

            // Sample Rate
            if (options.NetworkSampleRate <= 0)
            {
                return false;
            }

            if (options.NetworkSampleRate >= 1.0)
            {
                return true;
            }

            var randomValue = UnityEngine.Random.value;
            return randomValue <= options.NetworkSampleRate;
        }

        private static bool HasHttpMethodFilters(AlibabacloudOptions options)
        {
            return options.TrackHttpGet || options.TrackHttpPost || options.TrackHttpPut ||
                   options.TrackHttpDelete || options.TrackHttpPatch || options.TrackHttpHead ||
                   options.TrackHttpOptions;
        }

        private static bool IsHttpMethodAllowed(AlibabacloudOptions options, string method)
        {
            if (string.IsNullOrEmpty(method))
            {
                return false;
            }

            var methodUpper = method.ToUpperInvariant();
            switch (methodUpper)
            {
                case "GET":
                    return options.TrackHttpGet;
                case "POST":
                    return options.TrackHttpPost;
                case "PUT":
                    return options.TrackHttpPut;
                case "DELETE":
                    return options.TrackHttpDelete;
                case "PATCH":
                    return options.TrackHttpPatch;
                case "HEAD":
                    return options.TrackHttpHead;
                case "OPTIONS":
                    return options.TrackHttpOptions;
                default:
                    return false;
            }
        }

        public static bool ShouldTrackStatusCode(AlibabacloudOptions options, int statusCode)
        {
            if (options.NetworkStatusCodeRanges == null || options.NetworkStatusCodeRanges.Length == 0)
            {
                return true;
            }

            foreach (var range in options.NetworkStatusCodeRanges)
            {
                if (string.IsNullOrEmpty(range))
                {
                    continue;
                }

                var parts = range.Split('-');
                if (parts.Length == 2)
                {
                    if (int.TryParse(parts[0].Trim(), out var min) && int.TryParse(parts[1].Trim(), out var max))
                    {
                        if (statusCode >= min && statusCode <= max)
                        {
                            return true;
                        }
                    }
                }
                else if (int.TryParse(range.Trim(), out var exactCode))
                {
                    if (statusCode == exactCode)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        public static Dictionary<string, IEnumerable<string>> CaptureHeaders(
            System.Net.Http.Headers.HttpHeaders headers,
            string[] allowlist,
            string[] blocklist)
        {
            var result = new Dictionary<string, IEnumerable<string>>();

            if (headers == null)
            {
                return result;
            }

            foreach (var header in headers)
            {
                var headerName = header.Key.ToLowerInvariant();

                // Check blocklist
                if (blocklist != null && blocklist.Length > 0)
                {
                    var blocked = false;
                    foreach (var blockedHeader in blocklist)
                    {
                        if (!string.IsNullOrEmpty(blockedHeader) && 
                            headerName == blockedHeader.ToLowerInvariant().Trim())
                        {
                            blocked = true;
                            break;
                        }
                    }
                    if (blocked)
                    {
                        continue;
                    }
                }

                // Check allowlist
                if (allowlist != null && allowlist.Length > 0)
                {
                    var allowed = false;
                    foreach (var allowedHeader in allowlist)
                    {
                        if (!string.IsNullOrEmpty(allowedHeader) && 
                            headerName == allowedHeader.ToLowerInvariant().Trim())
                        {
                            allowed = true;
                            break;
                        }
                    }
                    if (!allowed)
                    {
                        continue;
                    }
                }

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

            return result;
        }

        public static string GenerateResourceKey(string url)
        {
            return string.IsNullOrEmpty(url)
                ? System.Guid.NewGuid().ToString()
                : $"{url}-{System.Guid.NewGuid():N}";
        }

        public static System.Collections.Generic.Dictionary<string, object> BuildStartAttributes(AlibabacloudOptions options, HttpRequestMessage request)
        {
            var attributes = new System.Collections.Generic.Dictionary<string, object>
            {
                { "network.url", request.RequestUri?.ToString() ?? string.Empty },
                { "network.method", request.Method.Method },
            };

            // Capture request body size
            if (options.CaptureRequestBodySize && request.Content != null)
            {
                try
                {
                    var contentBytes = request.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult();
                    attributes["network.request_body_size"] = contentBytes?.LongLength ?? 0L;
                }
                catch
                {
                    // ignore failures to read body size
                }
            }

            // Capture request headers
            if (options.CaptureRequestHeaders && request.Headers != null)
            {
                var requestHeaders = CaptureHeaders(
                    request.Headers,
                    options.NetworkRequestHeaderAllowlist,
                    options.NetworkRequestHeaderBlocklist);
                
                foreach (var header in requestHeaders)
                {
                    attributes[$"network.request_header.{header.Key}"] = string.Join(", ", header.Value);
                }
            }

            return attributes;
        }
    }
}
