using molilian.core;
using dodohold.core;
using Dapper;
using Microsoft.AspNetCore.Mvc;
using Org.BouncyCastle.Ocsp;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text.Json;
using System.Runtime.InteropServices;
using System.Net;
using System.Threading;
using System.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using TencentCloud.Soe.V20180724.Models;
using static dodohold.core.ZTOExpress.CreateOrderArgs;
using static Spire.Xls.Core.Spreadsheet.HTMLOptions;
using System.Net.Http.Json;
using System.Text.RegularExpressions;
using System.Text;
using TencentCloud.Oceanus.V20190422.Models;
using YunhuiKit;
using TencentCloud.Cdn.V20180606.Models;

namespace molilian.api.Controllers
{
    [ApiController]
    [Route("[controller]/[action]")]
    public class TestController : ControllerBase
    {
        private const string JdStressDeeplinkContent = "openapp.jdmobile://virtual?params=%7B%22category%22%3A%22jump%22%2C%22des%22%3A%22productDetail%22%2C%22skuId%22%3A%2210139003162781%22%2C%22sourceType%22%3A%22Item%22%2C%22sourceValue%22%3A%22view-ware%22%7D";

        protected IHttpContextAccessor _accessor;
        public TestController(IHttpContextAccessor accessor)
        {
            _accessor = accessor;
        }

        [HttpGet]
        public async Task<ActionResult> jd_unsafe_parse_stress([FromQuery] int t = 5, [FromQuery] int time = 60, [FromQuery] string ip = "127.0.0.1", [FromQuery] string oaid = "", [FromQuery] bool wait = false)
        {
            t = t <= 0 ? 5 : t;
            time = time <= 0 ? 60 : time;

            string runId = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff");
            string logName = $"run_{runId}_t{t}_time{time}";

            string startLogStatus = await SaveJdStressStartLogAsync(runId, logName, t, time, ip, oaid, wait);

            if (wait)
            {
                var result = await RunJdStressAsync(runId, logName, t, time, ip, oaid, startLogStatus, CancellationToken.None);
                return new APIResult(result);
            }

            _ = Task.Run(async () =>
            {
                try
                {
                    await RunJdStressAsync(runId, logName, t, time, ip, oaid, startLogStatus, CancellationToken.None);
                }
                catch (Exception ex)
                {
                    _ = new LoggerLibrary("jd_api_stress", $"{logName}_runner_error")
                        .Info(ex.Message, ex.StackTrace)
                        .SaveAsync();
                }
            });

            return new APIResult(new
            {
                success = true,
                message = "后台压测已启动",
                runId,
                requestPerSecond = t,
                timeSeconds = time,
                expectedRequests = (long)t * time,
                wait,
                log = new
                {
                    type = "LoggerLibrary",
                    dir = "jd_api_stress",
                    name = logName,
                    status = startLogStatus
                }
            });
        }


        [HttpGet]
        public async Task<ActionResult> ecs_list_test()
        {
            var list = AliyunPoolCore.EcsList();
            return new APIResult(new
            {
                success = true,
                msg = "ok",
                list
            });
        }
         

        [HttpGet]
        public async Task<ActionResult> testReconnectionRedis()
        {
            string cacheKey = "test";
            RedisKit.SetAsync(cacheKey, 1, 3600);
            string val = await RedisKit.GetAsync<string>(cacheKey);
            return new APIResult(new { success = "ok", val });

        }

        [HttpGet]
        public async Task<ActionResult> ChangePublicIpByName(string nodeName)
        {

            var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>("nodeName=@nodeName", new { nodeName });
            if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });

            int aliyun_id = proxy_node.aliyun_id;
            string proxy_server = proxy_node.server;

            var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
            if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });


            var uri = new Uri(proxy_server);
            string privateIp = uri.Host;
            AliyunCore core = new AliyunCore(account);
            var success = await core.ChangePublicIpAsync(privateIp);
            return new APIResult(new { success = "ok" });
        }

        [HttpGet]
        public async Task<ActionResult> ChangePublicIp(int id)
        {
            id = id >= 20000 ? id - 20000 : id;

            var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>(id);
            if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });

            int aliyun_id = proxy_node.aliyun_id;
            string proxy_server = proxy_node.server;

            var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
            if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });



            var uri = new Uri(proxy_server);
            string privateIp = uri.Host;
            AliyunCore core = new AliyunCore(account);
            var success = await core.ChangePublicIpAsync(privateIp);
            return new APIResult(new { success = "ok" });
        }



        [HttpGet]
        //第一部 先加一个网卡，
        public async Task<ActionResult> CreateNetworkInterface(int id, string ecsid)
        {
            AliyunCore core = new AliyunCore(id);
            //创建弹性网卡并绑定公网IP
            var success = core.EcsCreateNetworkInterface("cn-beijing", ecsid);

            return new APIResult(new { success });
        }


        [HttpGet]
        //第二部 给网卡绑定很多个辅助ip
        public async Task<ActionResult> EcsAssignPrivateIpAddresses(int id, string ecsid, int count, bool isSecondary = true)
        {
            AliyunCore core = new AliyunCore(id);
            //创建弹性网卡并绑定公网IP
            var success = core.EcsAssignPrivateIpAddresses("cn-beijing", ecsid, count, !isSecondary);
            return new APIResult(new { success });
        }

        [HttpGet]
        public async Task<ActionResult> QueryAccountBalance()
        {
            AliyunPlus plus = new AliyunPlus("LTAI5tQbkTjtULQcrWGaw2VJ", "UIkkolVVEddooOKOIUByCqymZkK6ZA");
            var response = plus.QueryAccountBalance();
            var balance = response.Body.Data.AvailableAmount;
            return new APIResult(new { response });
        }

        //[HttpGet]
        //public async Task<ActionResult> test(string table = "tk_parse_logs_shop", int count = 100)
        //{
        //    //table = "tk_parse_logs_shop";
        //    //table = "tk_parse_logs_live";
        //    //table = "tk_parse_logs_video";

        //    string filter = "success=0 AND message='放弃转链' AND reason='非标准链接'";

        //    for (int i = 0; i < 100; i++)
        //    {
        //        try
        //        {
        //            var list = new DBContext.Table(table)
        //                .Where(filter, new { })
        //                .Limit(count).Select<TkDataDTO>();
        //            if (!list.Any())
        //            {

        //                return new APIResult(new
        //                {
        //                    success = false,
        //                    message = "所有任务完成"
        //                });

        //            }
        //            foreach (var item in list)
        //            {
        //                TkPoolDTO? account = TkPoolCore.GetOne(TkPoolCore.TkAction.parse);
        //                if (account == null)
        //                {
        //                    return new APIResult(new
        //                    {
        //                        success = false,
        //                        message = "没有工作账号"
        //                    });
        //                }

        //                var result = item.Convert2Json().Convert2Object<TkDataDTO>();

        //                result.accountId = account.id;
        //                result.accountName = account.company;

        //                var alimama = new AlimamaPlus(account);


        //                int timeout = alimama._config.rt_max;
        //                using var cts = new CancellationTokenSource();
        //                cts.CancelAfter(timeout);

        //                result = await alimama.UnionParseAsync(item.content, result);
        //                string reason = "非标准链接".Equals(result.reason) ? "非标准链接ok" : result.reason;

        //                new DBContext.Table(table)
        //                    .Add("linkType", (int)result.link_type)
        //                    .Add("rawContent", result.rawContent)
        //                    .Add("success", result.success)
        //                    .Add("message", result.message)
        //                    .Add("reason", reason)
        //                    .Add("content", result.content)
        //                    .Add("itemId", result.itemId)
        //                    .Add("itemName", result.itemName)
        //                    .Add("pic", result.pic)
        //                    .Add("couponAmount", result.couponAmount)
        //                    .Add("promotionPrice", result.promotionPrice)
        //                    .Add("taoToken", result.taoToken)
        //                    .Add("shortLinkurl", result.shortLinkurl)
        //                    .Add("deeplink_url", result.deeplink_url)
        //                    .Add("num_iid", result.num_iid)
        //                    .Add("elapsedTime", result.elapsedTime)
        //                    .Add("elapsedTime2", result.elapsedTime2)
        //                    .Add("elapsedTime3", result.elapsedTime3)
        //                    .Add("subCode", result.subCode)
        //                    .Where("id=@id", new { item.id })
        //                    .Update();
        //            }
        //        }
        //        catch
        //        {
        //        }
        //    }

        //    return new APIResult(new
        //    {
        //        success = true,
        //        message = "ok"
        //    });


        //}



        [HttpGet]
        public async Task<ActionResult> redu_douyin(string command)
        {
            ReduPlus plus = new ReduPlus("", "", "");
            var result = await plus.DouyinParse(command);
            return Content(result.Convert2Json());
        }

        [HttpGet]
        public async Task<ActionResult> redu_kuaishou(string command)
        {

            ReduPlus plus = new ReduPlus("", "", "");
            var result = await plus.KuaishouParse(command);
            return Content(result.Convert2Json());
        }



        [HttpPost]
        public async Task<ActionResult> test2([FromForm] string jsonContent, [FromForm] string testText)
        {

            // 用于存储输出结果的StringBuilder
            StringBuilder outputBuilder = new StringBuilder();

            // 解析JSON数据
            JsonDocument jsonDoc = JsonDocument.Parse(jsonContent);

            // 获取根元素
            JsonElement root = jsonDoc.RootElement;

            // 遍历规则
            foreach (JsonElement ruleSet in root.EnumerateArray())
            {

                string platformType = ruleSet.Read("platformType", string.Empty);
                string supplier = ruleSet.Read("supplier", string.Empty);
                outputBuilder.AppendLine($"平台类型: {platformType}, 供应商: {supplier}");

                JsonElement pwdRules = ruleSet.GetProperty("pwdRules");
                int idx = 0;
                foreach (JsonElement patternElement in pwdRules.EnumerateArray())
                {
                    string pattern = patternElement.GetString();
                    try
                    {
                        // 使用Regex类来编译正则表达式
                        Regex compiledPattern = new Regex(pattern);
                        if (compiledPattern.IsMatch(testText))
                        {
                            outputBuilder.AppendLine($"规则 {idx + 1}: 匹配\t{pattern}");
                        }
                        else
                        {
                            outputBuilder.AppendLine($"规则 {idx + 1}: 不匹配");
                        }
                    }
                    catch (Exception e)
                    {
                        outputBuilder.AppendLine($"规则 {idx + 1}: 正则表达式错误 - {e.Message}\t{pattern}");
                    }
                    idx++;
                }
            }

            outputBuilder.AppendLine("测试完成");

            return Content(outputBuilder.ToString());
        }


        [HttpGet]
        public async Task<ActionResult> ip(string ip)
        {

            string result = IP2RegionPlus.Search(ip);

            return new APIResult(new
            {
                success = true,
                message = result
            });
        }


        [HttpPost]
        public async Task<ActionResult> testreg([FromBody] JsonElement form)
        {
            var content = form.Read("s", string.Empty);

            string shortLinkurl = AlimamaPlus.GetTaobaoLink(content);
            bool is_tao_token = AlimamaPlus.MatchRegexes(content, []);
            bool is_other = AlimamaPlus.MatchOtherInfo(content, []);

            return new APIResult(new
            {
                shortLinkurl,
                is_tao_token,
                is_other
            });


        }
        //[HttpGet]
        //public async Task<ActionResult> comparison_tk([FromQuery] int count = 100)
        //{
        //    string cacheKey = ":lock_key:comparison_tk_logs";
        //    int last_id = RedisHelper.Get<int>(cacheKey);

        //    string filter = "id>@last_id";
        //    var result = new DBContext.Table("comparison_tk_logs")
        //        .Where(filter, new { last_id })
        //        .Page(count, 1)
        //        .Order("ID")
        //        .PageList<TkDataDTO>(false);

        //    var ip = "127.0.0.1";
        //    var oaid = "test-comparison_tk_logs";
        //    foreach (var item in result.List)
        //    {
        //        await UnionParseCore.TaobaoParseAsync(item.rawContent, ip, oaid);
        //        RedisHelper.Set(cacheKey, item.id, 10 * 86400);
        //    }
        //    if (result.Count < count)
        //    {
        //        cacheKey = ":lock_key:start_comparison_tk";
        //        RedisHelper.Set(cacheKey, 1, 600);
        //    }
        //    return new APIResult(new
        //    {
        //        success = true,
        //        message = "ok"
        //    });
        //}

        //[HttpGet]
        //public async Task<ActionResult> comparison_tk_raw([FromQuery] int count = 100)
        //{
        //    string cacheKey = ":lock_key:comparison_tk_logs_raw";
        //    int last_id = RedisHelper.Get<int>(cacheKey);

        //    string filter = "id>@last_id";
        //    var result = new DBContext.Table("comparison_tk_logs")
        //        .Where(filter, new { last_id })
        //        .Page(count, 1)
        //        .Order("ID")
        //        .PageList<TkDataDTO>(false);

        //    var ip = "127.0.0.1";
        //    var oaid = "test-comparison_tk_logs_raw";
        //    foreach (var item in result.List)
        //    {
        //        await UnionParseCore.TaobaoParseAsync(item.rawContent, ip, oaid);
        //        RedisHelper.Set(cacheKey, item.id, 10 * 86400);
        //    }
        //    if (result.Count < count)
        //    {
        //        cacheKey = ":lock_key:start_comparison_tk";
        //        RedisHelper.Set(cacheKey, 1, 600);
        //    }
        //    return new APIResult(new
        //    {
        //        success = true,
        //        message = "ok"
        //    });
        //}


        [HttpGet]
        public async Task<ActionResult> testTask()
        {
            string url = "https://www.taobao.com";
            TkDataDTO result = new TkDataDTO();
            result.message = "init";
            result.success = true;


            int timeout = 1000;
            using var cts = new CancellationTokenSource();
            cts.CancelAfter(timeout);


            var requestTask = Task.Run(() => AlimamaPlus.testTaskAsync(result, cts.Token), cts.Token);


            var delayTask = Task.Delay(timeout, cts.Token);
            var completedTask = await Task.WhenAny(requestTask, delayTask);
            if (completedTask == requestTask)
            {
                result = await requestTask;
            }
            else
            {
                cts.Cancel();
                result.success = false;
                result.message = "放弃转链";
                result.reason = "请求超时";
                result.itemName = "点击打开淘宝APP";
            }

            return new APIResult(new
            {
                success = true,
                message = "ok",
                result,
            });
        }

        [HttpGet]
        public async Task<ActionResult> testThread()
        {
            // 查看默认的最小和最大线程数
            ThreadPool.GetMinThreads(out int defaultMinWorker, out int defaultMinIOC);
            ThreadPool.GetMaxThreads(out int defaultMaxWorker, out int defaultMaxIOC);
            string tmp = $"Default Min worker threads: {defaultMinWorker}, Min I/O completion threads: {defaultMinIOC}";
            string tmp2 = $"Default Max worker threads: {defaultMaxWorker}, Max I/O completion threads: {defaultMaxIOC}";



            // 获取当前线程池中可用的工作线程数和 I/O 完成端口线程数
            ThreadPool.GetAvailableThreads(out int availableWorkerThreads, out int availableIOCompletionThreads);
            string tmp3 = $"Current available worker threads: {availableWorkerThreads}, available I/O completion threads: {availableIOCompletionThreads}";

            return new APIResult(new
            {
                tmp,
                tmp2,
                tmp3,
            });
        }

        [HttpPost]
        public async Task<ActionResult> test1([FromBody] JsonElement form)
        {

            NotifyCore.Notify(new NifyMessage
            {
                message = $"【淘宝联盟:test】cookie 掉线",
                priority = NifyMessagePriority.high,
                tags = ["red_circle"]
            });


            return new APIResult(new
            {
                success = true,
                message = "ok"
            });
        }

        [HttpGet]
        public async Task<ActionResult> RepairTkDailyAccountStats(
            DateTime startDate = default,
            DateTime endDate = default,
            string accountIds = "129,140",
            string extraNames = "搜同款_楚颜_128众杰科技,搜同款_广哲2",
            bool dryRun = false,
            bool repairDailyLogs = true,
            bool repairRedis = true,
            bool repairRedisNameKeys = true,
            int commandTimeoutSeconds = 600)
        {
            if (startDate == default) startDate = new DateTime(2026, 6, 16);
            if (endDate == default) endDate = DateTime.Now.Date;
            commandTimeoutSeconds = Math.Clamp(commandTimeoutSeconds, 30, 3600);

            startDate = startDate.Date;
            endDate = endDate.Date;
            if (endDate < startDate)
            {
                return new APIResult(new { success = false, message = "endDate 不能早于 startDate" });
            }

            var ids = (accountIds ?? string.Empty)
                .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
                .Select(item => int.TryParse(item, out int id) ? id : 0)
                .Where(id => id > 0)
                .Distinct()
                .ToArray();

            if (ids.Length == 0)
            {
                return new APIResult(new { success = false, message = "accountIds 不能为空" });
            }

            using var conn = CenterHub.GetOpenConnection();
            if (conn.State != ConnectionState.Open) conn.Open();

            var errors = new List<object>();
            var affectedNames = new HashSet<string>(StringComparer.Ordinal);
            var accounts = SqlMapper.Query<TkDailyRepairCountRow>(
                conn,
                "SELECT id accountId, company accountName FROM tk_pool WHERE id IN @ids",
                new { ids },
                commandTimeout: commandTimeoutSeconds).ToList();

            foreach (string name in accounts.Select(item => item.accountName).Where(item => !string.IsNullOrWhiteSpace(item)))
            {
                affectedNames.Add(name);
            }

            extraNames ??= string.Empty;
            foreach (string name in extraNames
                .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
                .Where(item => !string.IsNullOrWhiteSpace(item)))
            {
                affectedNames.Add(name);
            }

            foreach (string name in SqlMapper.Query<string>(
                conn,
                @"
SELECT DISTINCT accountName
FROM center_daily_logs
WHERE channel = 0
  AND accountId IN @ids
  AND log_date BETWEEN @startDate AND @endDate
  AND COALESCE(accountName,'')<>''",
                new { ids, startDate, endDate },
                commandTimeout: commandTimeoutSeconds))
            {
                affectedNames.Add(name);
            }

            var days = EachDay(startDate, endDate).ToList();
            var dailyReports = new List<object>();
            var endpointCountsByDate = new Dictionary<string, List<TkDailyRepairEndpointCountRow>>();
            foreach (var date in days)
            {
                try
                {
                    string tableName = $"tk_parse_logs_{date:yyyyMMdd}";
                    if (!TableExists(conn, tableName, commandTimeoutSeconds))
                    {
                        dailyReports.Add(new { date = date.ToString("yyyy-MM-dd"), tableName, skipped = true, reason = "table not exists" });
                        continue;
                    }

                    var endpointCounts = SqlMapper.Query<TkDailyRepairEndpointCountRow>(
                        conn,
                        $@"
SELECT
    l.accountId,
    COALESCE(p.company, MAX(l.accountName), '') accountName,
    COALESCE(l.end_point, '') end_point,
    COUNT(*) total_count,
    CAST(COALESCE(SUM(l.success = 1), 0) AS SIGNED) success_count,
    CAST(COALESCE(SUM(l.success = 0), 0) AS SIGNED) fail_count,
    CAST(COALESCE(SUM(l.message = '放弃转链' OR l.reason = '放弃转链'), 0) AS SIGNED) abandon_count
FROM {tableName} l
LEFT JOIN tk_pool p ON p.id = l.accountId
WHERE l.accountId IN @ids
GROUP BY l.accountId, p.company, l.end_point
ORDER BY l.accountId, l.end_point",
                        new { ids },
                        commandTimeout: commandTimeoutSeconds).ToList();
                    endpointCountsByDate[date.ToString("yyyyMMdd")] = endpointCounts;

                    var accountCounts = endpointCounts
                        .GroupBy(item => item.accountId)
                        .Select(group => new TkDailyRepairCountRow
                        {
                            accountId = group.Key,
                            accountName = accounts.FirstOrDefault(item => item.accountId == group.Key)?.accountName
                                ?? group.FirstOrDefault()?.accountName
                                ?? string.Empty,
                            total_count = group.Sum(item => item.total_count),
                            success_count = group.Sum(item => item.success_count),
                            fail_count = group.Sum(item => item.fail_count),
                            abandon_count = group.Sum(item => item.abandon_count)
                        })
                        .ToList();

                    int dailyLogRows = 0;
                    var allAccountCounts = ids
                        .Select(accountId => accountCounts.FirstOrDefault(item => item.accountId == accountId)
                            ?? new TkDailyRepairCountRow
                            {
                                accountId = accountId,
                                accountName = accounts.FirstOrDefault(item => item.accountId == accountId)?.accountName ?? string.Empty
                            })
                        .ToList();

                    foreach (var counts in allAccountCounts)
                    {
                        if (!dryRun && repairDailyLogs)
                        {
                            dailyLogRows += UpsertCenterDailyLog(conn, date, counts, commandTimeoutSeconds);
                        }
                    }

                    dailyReports.Add(new
                    {
                        date = date.ToString("yyyy-MM-dd"),
                        tableName,
                        skipped = false,
                        dailyLogRows,
                        source = "mysql:tk_parse_logs_yyyyMMdd",
                        counts = allAccountCounts,
                        endpointCounts
                    });
                }
                catch (Exception ex)
                {
                    var error = new { scope = "daily_log", date = date.ToString("yyyy-MM-dd"), error = FormatRepairError(ex) };
                    errors.Add(error);
                    dailyReports.Add(new { date = date.ToString("yyyy-MM-dd"), skipped = true, reason = "error", error });
                }
            }

            var endpointReports = new List<object>();
            if (repairRedis || repairRedisNameKeys)
            {
                var endpoints = EndPointCore.List(true)
                    .Where(node => node.status && node.is_public_api && !string.IsNullOrEmpty(EndPointCore.GetRedisServer(node)))
                    .ToList();

                foreach (var endpoint in endpoints)
                {
                    var redisServer = EndPointCore.GetRedisServer(endpoint);
                    var endpointReport = new List<object>();

                    try
                    {
                        await using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
                        var redis = scope.Client;

                        foreach (var date in days)
                        {
                            string dateKey = date.ToString("yyyyMMdd");
                            if (!endpointCountsByDate.TryGetValue(dateKey, out var endpointCounts))
                            {
                                continue;
                            }

                            foreach (int accountId in ids)
                            {
                                var counts = endpointCounts.FirstOrDefault(item =>
                                    item.accountId == accountId &&
                                    string.Equals(item.end_point, endpoint.name, StringComparison.Ordinal))
                                    ?? new TkDailyRepairEndpointCountRow
                                    {
                                        accountId = accountId,
                                        accountName = accounts.FirstOrDefault(item => item.accountId == accountId)?.accountName ?? string.Empty,
                                        end_point = endpoint.name
                                    };

                                if (!dryRun && repairRedis)
                                {
                                    await WriteParseCountKeysAsync(redis, $"tb_{accountId}", dateKey, counts);
                                }

                                endpointReport.Add(new
                                {
                                    date = date.ToString("yyyy-MM-dd"),
                                    bucket = $"tb_{accountId}",
                                    counts
                                });
                            }

                            if (repairRedisNameKeys)
                            {
                                foreach (string name in affectedNames)
                                {
                                    var before = await ReadParseCountKeysAsync(redis, name, dateKey);

                                    if (!dryRun)
                                    {
                                        await DeleteParseBucketKeysAsync(redis, name, dateKey);
                                    }

                                    endpointReport.Add(new
                                    {
                                        date = date.ToString("yyyy-MM-dd"),
                                        deletedBucket = name,
                                        before
                                    });
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        errors.Add(new { scope = "redis_endpoint", endpoint = endpoint.name, error = FormatRepairError(ex) });
                    }

                    endpointReports.Add(new
                    {
                        endpoint = endpoint.name,
                        endpoint.description,
                        dryRun,
                        deletedBuckets = endpointReport
                    });
                }
            }

            return new APIResult(new
            {
                success = errors.Count == 0,
                dryRun,
                accountIds = ids,
                extraNames,
                repairDailyLogs,
                repairRedis,
                repairRedisNameKeys,
                commandTimeoutSeconds,
                startDate = startDate.ToString("yyyy-MM-dd"),
                endDate = endDate.ToString("yyyy-MM-dd"),
                affectedNames = affectedNames.OrderBy(item => item).ToList(),
                mysql = dailyReports,
                redis = endpointReports,
                errors
            });
        }

        private static IEnumerable<DateTime> EachDay(DateTime startDate, DateTime endDate)
        {
            for (var date = startDate.Date; date <= endDate.Date; date = date.AddDays(1))
            {
                yield return date;
            }
        }

        private static bool TableExists(IDbConnection conn, string tableName, int commandTimeoutSeconds)
        {
            const string sql = @"
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = DATABASE()
  AND table_name = @tableName";
            return SqlMapper.ExecuteScalar<int>(
                conn,
                sql,
                new { tableName },
                commandTimeout: commandTimeoutSeconds) > 0;
        }

        private static int UpsertCenterDailyLog(IDbConnection conn, DateTime date, TkDailyRepairCountRow counts, int commandTimeoutSeconds)
        {
            if (string.IsNullOrWhiteSpace(counts.accountName)) return 0;

            string successPercentage = counts.total_count > 0 ? $"{counts.success_count / (double)counts.total_count * 100:f2}%" : string.Empty;
            string abandonPercentage = counts.total_count > 0 ? $"{counts.abandon_count / (double)counts.total_count * 100:f2}%" : string.Empty;

            var existingIds = SqlMapper.Query<int>(
                conn,
                @"
SELECT id
FROM center_daily_logs
WHERE channel = 0
  AND log_date = @date
  AND accountId = @accountId
ORDER BY id",
                new { date, counts.accountId },
                commandTimeout: commandTimeoutSeconds).ToList();

            if (existingIds.Count > 0)
            {
                int affectedRows = SqlMapper.Execute(
                    conn,
                    @"
UPDATE center_daily_logs
SET accountName = @accountName,
    parse_total_count = @totalCount,
    parse_success_count = @successCount,
    parse_abandon_count = @abandonCount,
    parse_success_percentage = @successPercentage,
    parse_abandon_percentage = @abandonPercentage,
    last_time = NOW()
WHERE id = @id",
                    new
                    {
                        id = existingIds[0],
                        counts.accountName,
                        totalCount = counts.total_count,
                        successCount = counts.success_count,
                        abandonCount = counts.abandon_count,
                        successPercentage,
                        abandonPercentage
                    },
                    commandTimeout: commandTimeoutSeconds);

                if (existingIds.Count > 1)
                {
                    affectedRows += SqlMapper.Execute(
                        conn,
                        "DELETE FROM center_daily_logs WHERE id IN @ids",
                        new { ids = existingIds.Skip(1).ToArray() },
                        commandTimeout: commandTimeoutSeconds);
                }

                return affectedRows;
            }

            if (counts.total_count <= 0) return 0;

            return SqlMapper.Execute(
                conn,
                @"
INSERT INTO center_daily_logs
    (channel, accountId, accountName, log_date, create_time, last_time,
     parse_total_count, parse_success_count, parse_abandon_count,
     parse_success_percentage, parse_abandon_percentage)
VALUES
    (0, @accountId, @accountName, @date, NOW(), NOW(),
     @totalCount, @successCount, @abandonCount,
     @successPercentage, @abandonPercentage)",
                new
                {
                    date,
                    counts.accountId,
                    counts.accountName,
                    totalCount = counts.total_count,
                    successCount = counts.success_count,
                    abandonCount = counts.abandon_count,
                    successPercentage,
                    abandonPercentage
                },
                commandTimeout: commandTimeoutSeconds);
        }

        private static async Task<TkDailyRepairCountRow> ReadParseCountKeysAsync(YunhuiKit.RedisClient redis, string bucket, string dateKey)
        {
            return new TkDailyRepairCountRow
            {
                accountName = bucket,
                total_count = await redis.GetAsync<int>($":parse_total:{bucket}:{dateKey}"),
                success_count = await redis.GetAsync<int>($":parse_total:{bucket}:success:{dateKey}"),
                fail_count = await redis.GetAsync<int>($":parse_total:{bucket}:fail:{dateKey}"),
                abandon_count = await redis.GetAsync<int>($":parse_total:{bucket}:放弃转链:{dateKey}")
            };
        }

        private static async Task DeleteParseBucketKeysAsync(YunhuiKit.RedisClient redis, string bucket, string dateKey)
        {
            var keys = new List<string>
            {
                $":parse_total:{bucket}:{dateKey}",
                $":parse_total:{bucket}:success:{dateKey}",
                $":parse_total:{bucket}:fail:{dateKey}",
                $":parse_total:{bucket}:放弃转链:{dateKey}",
                $":parse_total:{bucket}:message:{dateKey}",
                $":parse_total:{bucket}:reason:{dateKey}"
            };

            foreach (string dpBucket in new[] { "dp_none", "dp_home", "dp_success", "dp_fail" })
            {
                keys.Add($":parse_total:{dpBucket}:{bucket}:{dateKey}");
                keys.Add($":parse_total:{dpBucket}:{bucket}:success:{dateKey}");
                keys.Add($":parse_total:{dpBucket}:{bucket}:fail:{dateKey}");
                keys.Add($":parse_total:{dpBucket}:{bucket}:放弃转链:{dateKey}");
                keys.Add($":parse_total:{dpBucket}:{bucket}:message:{dateKey}");
                keys.Add($":parse_total:{dpBucket}:{bucket}:reason:{dateKey}");
            }

            await redis.DelAsync(keys.ToArray());
        }

        private static string FormatRepairError(Exception ex)
        {
            return ex.InnerException == null ? ex.Message : $"{ex.Message} | {ex.InnerException.Message}";
        }

        private static async Task WriteParseCountKeysAsync(YunhuiKit.RedisClient redis, string bucket, string dateKey, TkDailyRepairCountRow counts)
        {
            var keys = new[]
            {
                $":parse_total:{bucket}:{dateKey}",
                $":parse_total:{bucket}:success:{dateKey}",
                $":parse_total:{bucket}:fail:{dateKey}",
                $":parse_total:{bucket}:放弃转链:{dateKey}"
            };

            if (counts.total_count <= 0)
            {
                await redis.DelAsync(keys);
                return;
            }

            await redis.SetAsync(keys[0], counts.total_count, 90 * 86400);
            await redis.SetAsync(keys[1], counts.success_count, 90 * 86400);
            await redis.SetAsync(keys[2], counts.fail_count, 90 * 86400);
            await redis.SetAsync(keys[3], counts.abandon_count, 90 * 86400);
        }

        private class TkDailyRepairCountRow
        {
            public int accountId { get; set; }
            public string accountName { get; set; } = string.Empty;
            public long total_count { get; set; }
            public long success_count { get; set; }
            public long fail_count { get; set; }
            public long abandon_count { get; set; }
        }

        private sealed class TkDailyRepairEndpointCountRow : TkDailyRepairCountRow
        {
            public string end_point { get; set; } = string.Empty;
        }

        private static async Task<string> SaveJdStressStartLogAsync(string runId, string logName, int t, int time, string ip, string oaid, bool wait)
        {
            try
            {
                await new LoggerLibrary("jd_api_stress", logName)
                    .AppendLine($"status=started")
                    .AppendLine($"runId={runId}")
                    .AppendLine($"startedAt={DateTime.Now:O}")
                    .AppendLine($"t={t}")
                    .AppendLine($"timeSeconds={time}")
                    .AppendLine($"ip={ip}")
                    .AppendLine($"oaid={oaid}")
                    .AppendLine($"wait={wait}")
                    .AppendLine($"content={JdStressDeeplinkContent}")
                    .SaveAsync();
                return "started";
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }

        private static async Task<JdStressRunResult> RunJdStressAsync(string runId, string logName, int t, int time, string ip, string oaid, string startLogStatus, CancellationToken cancellationToken)
        {
            var startedAt = DateTime.Now;
            var stopwatch = Stopwatch.StartNew();

            long scheduledCount = 0;
            long completedCount = 0;
            long successCount = 0;
            long failCount = 0;
            long exceptionCount = 0;
            long riskControlCount = 0;
            long totalElapsedMs = 0;
            int activeCount = 0;
            int peakActiveCount = 0;

            var responseLines = new ConcurrentQueue<string>();
            var messageCounts = new ConcurrentDictionary<string, int>(StringComparer.Ordinal);
            var codeCounts = new ConcurrentDictionary<string, int>(StringComparer.Ordinal);
            int initialTaskCapacity = (int)Math.Min((long)t * Math.Min(time, 60), 4096L);
            var runningTasks = new List<Task>(initialTaskCapacity);
            string message = "ok";

            try
            {
                for (int second = 0; second < time && !cancellationToken.IsCancellationRequested; second++)
                {
                    for (int i = 0; i < t; i++)
                    {
                        long sequence = Interlocked.Increment(ref scheduledCount);
                        runningTasks.Add(Task.Run(async () =>
                        {
                            int currentActive = Interlocked.Increment(ref activeCount);
                            UpdateMax(ref peakActiveCount, currentActive);

                            try
                            {
                                var item = await ExecuteJdStressRequestAsync(sequence, ip, oaid, cancellationToken);
                                Interlocked.Increment(ref completedCount);
                                Interlocked.Add(ref totalElapsedMs, item.ElapsedMs);

                                if (item.Success)
                                {
                                    Interlocked.Increment(ref successCount);
                                }
                                else
                                {
                                    Interlocked.Increment(ref failCount);
                                }

                                if (item.IsException)
                                {
                                    Interlocked.Increment(ref exceptionCount);
                                }

                                if (IsRiskControlResult(item))
                                {
                                    Interlocked.Increment(ref riskControlCount);
                                }

                                string messageKey = string.IsNullOrWhiteSpace(item.SubMessage)
                                    ? item.Message
                                    : $"{item.Message}:{item.SubMessage}";
                                if (string.IsNullOrWhiteSpace(messageKey)) messageKey = "empty";
                                messageCounts.AddOrUpdate(messageKey, 1, (_, value) => value + 1);
                                codeCounts.AddOrUpdate(item.Code.ToString(), 1, (_, value) => value + 1);

                                responseLines.Enqueue(
                                    $"{item.FinishedAt:O}\tseq={item.Sequence}\telapsedMs={item.ElapsedMs}\tcode={item.Code}\tsuccess={item.Success}\tmessage={NormalizeLogValue(item.Message)}\tsub_message={NormalizeLogValue(item.SubMessage)}\tresponse={NormalizeLogValue(item.Response)}");
                            }
                            finally
                            {
                                Interlocked.Decrement(ref activeCount);
                            }
                        }));
                    }

                    await ObserveCompletedTasksAsync(runningTasks);

                    var nextTick = TimeSpan.FromSeconds(second + 1);
                    var delay = nextTick - stopwatch.Elapsed;
                    if (delay > TimeSpan.Zero)
                    {
                        await Task.Delay(delay, cancellationToken);
                    }
                }
            }
            catch (OperationCanceledException ex)
            {
                message = $"canceled:{ex.Message}";
                responseLines.Enqueue($"{DateTime.Now:O}\tmessage={NormalizeLogValue(message)}");
            }
            catch (Exception ex)
            {
                message = $"runner error:{ex.Message}";
                responseLines.Enqueue($"{DateTime.Now:O}\tmessage={NormalizeLogValue(message)}\tstack={NormalizeLogValue(ex.StackTrace ?? string.Empty)}");
            }

            try
            {
                await Task.WhenAll(runningTasks);
            }
            catch (Exception ex)
            {
                message = $"task wait error:{ex.Message}";
                responseLines.Enqueue($"{DateTime.Now:O}\tmessage={NormalizeLogValue(message)}\tstack={NormalizeLogValue(ex.StackTrace ?? string.Empty)}");
            }

            stopwatch.Stop();

            string logStatus = "saved";
            try
            {
                var log = new LoggerLibrary("jd_api_stress", logName);
                log.AppendLine($"status=finished");
                log.AppendLine($"startLogStatus={startLogStatus}");
                log.AppendLine($"runId={runId}");
                log.AppendLine($"startedAt={startedAt:O}");
                log.AppendLine($"finishedAt={DateTime.Now:O}");
                log.AppendLine($"t={t}");
                log.AppendLine($"timeSeconds={time}");
                log.AppendLine($"ip={ip}");
                log.AppendLine($"oaid={oaid}");
                log.AppendLine($"content={JdStressDeeplinkContent}");
                log.AppendLine($"scheduled={scheduledCount}");
                log.AppendLine($"completed={completedCount}");
                log.AppendLine($"success={successCount}");
                log.AppendLine($"fail={failCount}");
                log.AppendLine($"exception={exceptionCount}");
                log.AppendLine($"riskControl={riskControlCount}");
                log.AppendLine($"peakActive={peakActiveCount}");
                log.AppendLine("messageCounts=");
                foreach (var item in messageCounts.OrderByDescending(item => item.Value))
                {
                    log.AppendLine($"{item.Key}\t{item.Value}");
                }
                log.AppendLine("codeCounts=");
                foreach (var item in codeCounts.OrderByDescending(item => item.Value))
                {
                    log.AppendLine($"{item.Key}\t{item.Value}");
                }
                log.AppendLine("responses=");
                while (responseLines.TryDequeue(out string? line))
                {
                    log.AppendLine(line);
                }
                await log.SaveAsync();
            }
            catch (Exception ex)
            {
                logStatus = ex.Message;
            }

            return new JdStressRunResult
            {
                success = true,
                message = message,
                runId = runId,
                requestPerSecond = t,
                timeSeconds = time,
                expectedRequests = (long)t * time,
                scheduledCount = scheduledCount,
                completedCount = completedCount,
                successCount = successCount,
                failCount = failCount,
                exceptionCount = exceptionCount,
                riskControlCount = riskControlCount,
                peakActiveCount = peakActiveCount,
                averageElapsedMs = completedCount == 0 ? 0 : Math.Round(totalElapsedMs / (double)completedCount, 2),
                elapsedSeconds = Math.Round(stopwatch.Elapsed.TotalSeconds, 2),
                messageCounts = messageCounts.OrderByDescending(item => item.Value).ToDictionary(item => item.Key, item => item.Value),
                codeCounts = codeCounts.OrderByDescending(item => item.Value).ToDictionary(item => item.Key, item => item.Value),
                log = new JdStressLogInfo
                {
                    type = "LoggerLibrary",
                    dir = "jd_api_stress",
                    name = logName,
                    status = logStatus
                }
            };
        }

        private static async Task ObserveCompletedTasksAsync(List<Task> runningTasks)
        {
            for (int i = runningTasks.Count - 1; i >= 0; i--)
            {
                if (!runningTasks[i].IsCompleted) continue;

                await runningTasks[i];
                runningTasks.RemoveAt(i);
            }
        }

        private static async Task<JdStressRequestResult> ExecuteJdStressRequestAsync(long sequence, string ip, string oaid, CancellationToken cancellationToken)
        {
            var sw = Stopwatch.StartNew();
            try
            {
                var request = new UnionParseRequest
                {
                    Content = JdStressDeeplinkContent,
                    Channel = "jd",
                    CommerceType = 0,
                    Ip = ip,
                    Oaid = oaid,
                    RiskStrategy = string.Empty,
                    LaunchScene = 0,
                    AccountId = 0,
                    SpecialText = 0,
                    QueryText = string.Empty,
                    ClickId = string.Empty,
                    Type = "dp",
                    Pic = string.Empty
                };

                var result = await UnionParseCore.DeeplinkJdParseAsync(request, cancellationToken);
                sw.Stop();

                string response = result.Content ?? string.Empty;
                var parsed = ParseJdStressResponse(response);
                parsed.Sequence = sequence;
                parsed.ElapsedMs = sw.ElapsedMilliseconds;
                parsed.Response = response;
                parsed.FinishedAt = DateTime.Now;
                return parsed;
            }
            catch (Exception ex)
            {
                sw.Stop();
                return new JdStressRequestResult
                {
                    Sequence = sequence,
                    ElapsedMs = sw.ElapsedMilliseconds,
                    Success = false,
                    Message = "exception",
                    SubMessage = ex.Message,
                    Code = 0,
                    Response = ex.ToString(),
                    FinishedAt = DateTime.Now,
                    IsException = true
                };
            }
        }

        private static JdStressRequestResult ParseJdStressResponse(string response)
        {
            var result = new JdStressRequestResult();
            if (string.IsNullOrWhiteSpace(response))
            {
                result.Message = "empty response";
                return result;
            }

            try
            {
                using var doc = JsonDocument.Parse(response);
                var root = doc.RootElement;
                result.Success = ReadBool(root, "success");
                result.Message = ReadString(root, "message");
                result.SubMessage = ReadString(root, "sub_message");
                result.Code = ReadInt(root, "code");
            }
            catch (Exception ex)
            {
                result.Success = false;
                result.Message = "parse response error";
                result.SubMessage = ex.Message;
                result.Code = 0;
            }
            return result;
        }

        private static bool ReadBool(JsonElement root, string propertyName)
        {
            if (!root.TryGetProperty(propertyName, out var property)) return false;

            return property.ValueKind switch
            {
                JsonValueKind.True => true,
                JsonValueKind.False => false,
                JsonValueKind.Number => property.TryGetInt32(out int value) && value != 0,
                JsonValueKind.String => bool.TryParse(property.GetString(), out bool value) && value,
                _ => false
            };
        }

        private static int ReadInt(JsonElement root, string propertyName)
        {
            if (!root.TryGetProperty(propertyName, out var property)) return 0;

            return property.ValueKind switch
            {
                JsonValueKind.Number => property.TryGetInt32(out int value) ? value : 0,
                JsonValueKind.String => int.TryParse(property.GetString(), out int value) ? value : 0,
                _ => 0
            };
        }

        private static string ReadString(JsonElement root, string propertyName)
        {
            if (!root.TryGetProperty(propertyName, out var property)) return string.Empty;
            if (property.ValueKind == JsonValueKind.Null || property.ValueKind == JsonValueKind.Undefined) return string.Empty;
            return property.ValueKind == JsonValueKind.String ? property.GetString() ?? string.Empty : property.ToString();
        }

        private static bool IsRiskControlResult(JdStressRequestResult result)
        {
            if (!"放弃转链".Equals(result.Message, StringComparison.Ordinal)) return false;

            return result.SubMessage.Contains("控制", StringComparison.Ordinal)
                || result.SubMessage.Contains("风控", StringComparison.Ordinal)
                || result.SubMessage.Contains("限流", StringComparison.Ordinal)
                || result.SubMessage.Contains("频", StringComparison.Ordinal)
                || result.SubMessage.Contains("系统繁忙", StringComparison.Ordinal);
        }

        private static string NormalizeLogValue(string value)
        {
            return (value ?? string.Empty)
                .Replace("\r", "\\r")
                .Replace("\n", "\\n")
                .Replace("\t", " ");
        }

        private static void UpdateMax(ref int target, int value)
        {
            int snapshot;
            while (value > (snapshot = Volatile.Read(ref target))
                   && Interlocked.CompareExchange(ref target, value, snapshot) != snapshot)
            {
            }
        }

        private sealed class JdStressRequestResult
        {
            public long Sequence { get; set; }
            public bool Success { get; set; }
            public string Message { get; set; } = string.Empty;
            public string SubMessage { get; set; } = string.Empty;
            public int Code { get; set; }
            public long ElapsedMs { get; set; }
            public string Response { get; set; } = string.Empty;
            public DateTime FinishedAt { get; set; }
            public bool IsException { get; set; }
        }

        private sealed class JdStressRunResult
        {
            public bool success { get; set; }
            public string message { get; set; } = string.Empty;
            public string runId { get; set; } = string.Empty;
            public int requestPerSecond { get; set; }
            public int timeSeconds { get; set; }
            public long expectedRequests { get; set; }
            public long scheduledCount { get; set; }
            public long completedCount { get; set; }
            public long successCount { get; set; }
            public long failCount { get; set; }
            public long exceptionCount { get; set; }
            public long riskControlCount { get; set; }
            public int peakActiveCount { get; set; }
            public double averageElapsedMs { get; set; }
            public double elapsedSeconds { get; set; }
            public Dictionary<string, int> messageCounts { get; set; } = [];
            public Dictionary<string, int> codeCounts { get; set; } = [];
            public JdStressLogInfo log { get; set; } = new();
        }

        private sealed class JdStressLogInfo
        {
            public string type { get; set; } = string.Empty;
            public string dir { get; set; } = string.Empty;
            public string name { get; set; } = string.Empty;
            public string status { get; set; } = string.Empty;
        }

        [HttpGet]
        public async Task<ActionResult> xxx()
        {
            var list = await TkPoolCore.ListAsync();
            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
            string message = string.Empty;
            foreach (var account in list)
            {
                try
                {
                    var alimama = new AlimamaPlus(account);
                    alimama.RenewCookie();
                }
                catch (Exception ex)
                {
                    message = $"【cookie续期】xxxx\n{ex.Message}\n{ex.StackTrace}";
                    NotifyCore.Notify(new NifyMessage
                    {
                        message = message,
                        priority = NifyMessagePriority.high,
                        tags = ["red_circle"]
                    });
                    continue;
                }
            }
            return new APIResult(new { success = true, message = "ok" });
        }



    }
}
