using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Diagnostics;
using System.Net.Http;
using System.Text;

namespace SHH.CameraDashboard
{
    /// <summary>
    /// 全局 Web API 服务
    /// 职责：
    /// 1.  统一管理 HttpClient 实例，避免资源耗尽。
    /// 2.  封装标准的 CRUD (GET, POST, PUT, DELETE, PATCH) 操作。
    /// 3.  提供详细的请求日志（耗时、状态码、请求/响应内容）。
    /// 4.  处理超时和异常，提供一致的错误处理机制。
    /// </summary>
    public class WebApiService
    {
        #region --- 静态成员 ---

        /// <summary>
        /// WebAPI 设为单例
        /// </summary>
        public static WebApiService Instance { get; } = new WebApiService();
        
        /// <summary>
        /// 静态 HttpClient 实例。
        /// 最佳实践：应用程序应共享单个 HttpClient 实例以避免套接字耗尽。
        /// </summary>
        private static readonly HttpClient _client;

        #endregion

        #region --- 实例成员 ---

        /// <summary>
        /// 当一个请求完成（无论成功或失败）时发生。
        /// 可用于记录详细的 API 调用日志。
        /// </summary>
        public event Action<LogWebApiModel>? OnRequestCompleted;

        /// <summary>
        /// 日志内容的最大长度（5KB），防止过大的响应体撑爆内存。
        /// </summary>
        private const int MAX_LOG_LENGTH = 5120;

        #endregion

        #region --- 构造函数 ---

        /// <summary>
        /// 构造函数
        /// </summary>
        private WebApiService()
        {
        }

        /// <summary>
        /// 静态构造函数，用于初始化 HttpClient。
        /// </summary>
        static WebApiService()
        {
            // 配置 HttpClient 处理器
            var handler = new SocketsHttpHandler
            {
                // 设置连接池寿命为 15 分钟。
                // 这有助于解决 DNS 变更后，HttpClient 仍使用旧 IP 的问题。
                PooledConnectionLifetime = TimeSpan.FromMinutes(15)
            };

            _client = new HttpClient(handler)
            {
                // 设置默认请求超时时间为 15 秒。
                Timeout = TimeSpan.FromSeconds(15)
            };
        }

        #endregion

        #region --- 核心 CRUD 方法 ---

        /// <summary>
        /// 发送一个 GET 请求。
        /// </summary>
        /// <param name="url">请求的完整 URL。</param>
        /// <param name="moduleName">发起请求的模块名称，用于日志分类。</param>
        /// <param name="token">用于取消操作的令牌。</param>
        /// <returns>成功时返回响应内容的字符串表示。</returns>
        public async Task<string> GetAsync(string url, string moduleName = "System", CancellationToken token = default, bool isAutoPost = false)
        {
            return await ExecuteRequestAsync(url, "GET", null, moduleName, token, async () =>
            {
                return await _client.GetAsync(url, token);
            }, isAutoPost);
        }

        /// <summary>
        /// 发送一个 POST 请求。
        /// </summary>
        /// <param name="url">请求的完整 URL。</param>
        /// <param name="jsonBody">请求体的 JSON 字符串。</param>
        /// <param name="moduleName">发起请求的模块名称，用于日志分类。</param>
        /// <param name="token">用于取消操作的令牌。</param>
        /// <returns>成功时返回响应内容的字符串表示。</returns>
        public async Task<string> PostAsync(string url, string jsonBody, string moduleName = "System", CancellationToken token = default)
        {
            return await ExecuteRequestAsync(url, "POST", jsonBody, moduleName, token, async () =>
            {
                using var content = CreateJsonContent(jsonBody);
                return await _client.PostAsync(url, content, token);
            });
        }

        /// <summary>
        /// 发送一个 PUT 请求（通常用于替换整个资源）。
        /// </summary>
        /// <param name="url">请求的完整 URL。</param>
        /// <param name="jsonBody">请求体的 JSON 字符串。</param>
        /// <param name="moduleName">发起请求的模块名称，用于日志分类。</param>
        /// <param name="token">用于取消操作的令牌。</param>
        /// <returns>成功时返回响应内容的字符串表示。</returns>
        public async Task<string> PutAsync(string url, string jsonBody, string moduleName = "System", CancellationToken token = default)
        {
            return await ExecuteRequestAsync(url, "PUT", jsonBody, moduleName, token, async () =>
            {
                using var content = CreateJsonContent(jsonBody);
                return await _client.PutAsync(url, content, token);
            });
        }

        /// <summary>
        /// 发送一个 PATCH 请求（通常用于更新资源的部分属性）。
        /// </summary>
        /// <param name="url">请求的完整 URL。</param>
        /// <param name="jsonBody">请求体的 JSON 字符串。</param>
        /// <param name="moduleName">发起请求的模块名称，用于日志分类。</param>
        /// <param name="token">用于取消操作的令牌。</param>
        /// <returns>成功时返回响应内容的字符串表示。</returns>
        public async Task<string> PatchAsync(string url, string jsonBody, string moduleName = "System", CancellationToken token = default)
        {
            return await ExecuteRequestAsync(url, "PATCH", jsonBody, moduleName, token, async () =>
            {
                using var content = CreateJsonContent(jsonBody);
                return await _client.PatchAsync(url, content, token);
            });
        }

        /// <summary>
        /// 发送一个 DELETE 请求。
        /// </summary>
        /// <param name="url">请求的完整 URL。</param>
        /// <param name="moduleName">发起请求的模块名称，用于日志分类。</param>
        /// <param name="token">用于取消操作的令牌。</param>
        /// <returns>成功时返回响应内容的字符串表示。</returns>
        public async Task<string> DeleteAsync(string url, string moduleName = "System", CancellationToken token = default)
        {
            return await ExecuteRequestAsync(url, "DELETE", null, moduleName, token, async () =>
            {
                return await _client.DeleteAsync(url, token);
            });
        }

        #endregion

        #region --- 私有辅助方法 ---

        /// <summary>
        /// 创建一个 UTF-8 编码的 JSON 内容对象。
        /// </summary>
        private StringContent CreateJsonContent(string json)
        {
            return new StringContent(json ?? "", Encoding.UTF8, "application/json");
        }

        /// <summary>
        /// 核心执行引擎，封装了所有 HTTP 请求的通用逻辑：计时、日志、异常处理。
        /// </summary>
        private async Task<string> ExecuteRequestAsync(
            string url,
            string method,
            string? requestData,
            string moduleName,
            CancellationToken token,
            Func<Task<HttpResponseMessage>> action,
            bool isAutoPost = false)
        {
            if (requestData == null)
                requestData = "(请求数据为空)";

            var log = new LogWebApiModel
            {
                Time = DateTime.Now,
                Method = method,
                Url = url,
                RequestData = TruncateLog(requestData),
                AppModule = moduleName,
                IsAutoPost = isAutoPost,
            };

            var sw = Stopwatch.StartNew();

            try
            {
                // 执行具体的 HTTP 请求
                using var response = await action();

                // 读取响应内容
                var responseString = await response.Content.ReadAsStringAsync(token);

                // 更新日志
                log.StatusCode = ((int)response.StatusCode).ToString();
                log.ResponseData = TruncateLog(responseString);

                // 如果 HTTP 状态码不是 2xx，则抛出异常
                response.EnsureSuccessStatusCode();

                return responseString;
            }
            catch (Exception ex)
            {
                // 捕获所有异常，更新日志状态为 "Error"
                log.StatusCode = "Error";
                log.ResponseData = $"Client Exception: {ex.Message}";

                // 重新抛出异常，让调用者知道请求失败
                throw;
            }
            finally
            {
                // 无论成功或失败，都记录耗时并触发日志事件
                sw.Stop();
                log.ElapsedMilliseconds = sw.ElapsedMilliseconds;

                // 安全地触发事件，防止订阅者抛出异常影响主线程
                try
                {
                    OnRequestCompleted?.Invoke(log);
                }
                catch (Exception logEx)
                {
                    // 记录日志系统自身的错误，但不中断程序
                    Debug.WriteLine($"Log System Error: {logEx.Message}");
                }
            }
        }
        /// <summary>
        /// 截断过长的日志内容，并尝试格式化 JSON 以便阅读。
        /// </summary>
        private string TruncateLog(string? content)
        {
            if (string.IsNullOrEmpty(content)) return string.Empty;

            // 1. 【新增步骤】尝试将内容格式化为漂亮的 JSON
            // 注意：格式化会增加空格和换行，导致长度变长，这是符合预期的
            string finalContent = TryFormatJson(content);

            // 2. 截断逻辑 (基于格式化后的长度)
            if (finalContent.Length > MAX_LOG_LENGTH)
            {
                // 保留前 N 个字符，并在末尾添加提示
                return finalContent.Substring(0, MAX_LOG_LENGTH) +
                       $"\r\n\r\n... [日志过长已截断，显示前 {MAX_LOG_LENGTH} 字符]";
            }

            return finalContent;
        }

        /// <summary>
        /// 尝试将字符串解析并格式化为缩进的 JSON。
        /// 如果解析失败（说明不是 JSON），则原样返回。
        /// </summary>
        private string TryFormatJson(string content)
        {
            // 简单的性能优化：如果不像 JSON（不以 { 或 [ 开头），直接跳过
            var trimmed = content.Trim();
            if ((trimmed.StartsWith("{") && trimmed.EndsWith("}")) ||
                (trimmed.StartsWith("[") && trimmed.EndsWith("]")))
            {
                try
                {
                    // 使用 JToken 解析，它可以处理 Object {} 和 Array []
                    var token = JToken.Parse(content);

                    // 格式化输出 (Indented = 缩进模式)
                    return token.ToString(Formatting.Indented);
                }
                catch
                {
                    // 解析失败（可能只是普通的文本消息，或者 HTML），忽略异常，返回原样
                    return content;
                }
            }

            return content;
        }

        #endregion
    }
}