using Dapper;
using dodohold.core;
using System.Text;
using System.Text.Json;

namespace molilian.core
{
    public static class PromotionQuerySamplingCore
    {
        private const string SamplingRemainingKey = "promotion_query_sampling:remaining";
        private const int SamplingSwitchExpireSeconds = 7 * 86400;
        private const int MaxEnableCount = 5000;
        public const int MaxQueryCount = 1100;

        private static readonly SemaphoreSlim EnsureTableLock = new(1, 1);
        private static readonly JsonSerializerOptions JsonOptions = new()
        {
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            WriteIndented = true
        };

        private static bool _tableEnsured = false;

        public static int EnableSampling(int count)
        {
            count = Math.Clamp(count, 0, MaxEnableCount);
            if (count == 0)
            {
                RedisHelper.Del(SamplingRemainingKey);
                return 0;
            }

            RedisHelper.Set(SamplingRemainingKey, count, SamplingSwitchExpireSeconds);
            return count;
        }

        public static int GetRemainingCount()
        {
            return Math.Max(0, RedisHelper.Get<int>(SamplingRemainingKey));
        }

        public static async Task TrySampleAsync(string imageBase64, string ip, string oaid, string responseJson)
        {
            if (string.IsNullOrWhiteSpace(imageBase64)
                && string.IsNullOrWhiteSpace(ip)
                && string.IsNullOrWhiteSpace(oaid)
                && string.IsNullOrWhiteSpace(responseJson))
            {
                return;
            }

            if (!TryConsumeQuota())
            {
                return;
            }

            string sampleId = BuildSampleId();
            try
            {
                await EnsureTableAsync();
                using var conn = DBContext.GetOpenConnection();
                const string sql = @"
INSERT INTO promotion_query_samples
    (id, ip, oaid, image_base64, response_json, create_time)
VALUES
    (@id, @ip, @oaid, @image_base64, @response_json, @create_time);";

                await conn.ExecuteAsync(sql, new
                {
                    id = sampleId,
                    ip = ip ?? string.Empty,
                    oaid = oaid ?? string.Empty,
                    image_base64 = imageBase64 ?? string.Empty,
                    response_json = responseJson ?? string.Empty,
                    create_time = DateTime.Now
                });
            }
            catch (Exception ex)
            {
                RedisHelper.IncrBy(SamplingRemainingKey);
                RedisHelper.Expire(SamplingRemainingKey, SamplingSwitchExpireSeconds);
                _ = new LoggerLibrary("promotion_query_samples", "save_error")
                    .Info(sampleId)
                    .Info(ex.Message, ex.StackTrace)
                    .SaveAsync();
            }
        }

        public static async Task<PromotionQuerySampleQueryResultDTO> QueryLogsAsync(int page, int pageSize, string keyword)
        {
            page = Math.Max(page, 1);
            pageSize = Math.Clamp(pageSize, 1, MaxQueryCount);
            keyword = NormalizeKeyword(keyword);

            await EnsureTableAsync();
            using var conn = DBContext.GetOpenConnection();

            string where = string.Empty;
            var args = new DynamicParameters();
            args.Add("count", pageSize);
            if (!string.IsNullOrWhiteSpace(keyword))
            {
                where = "WHERE id LIKE @keyword";
                args.Add("keyword", $"%{keyword}%");
            }

            string countSql = $@"
SELECT COUNT(*)
FROM promotion_query_samples
{where}";

            int totalCount = await conn.QuerySingleAsync<int>(countSql, args);
            int totalPages = totalCount <= 0 ? 0 : (int)Math.Ceiling(totalCount / (double)pageSize);
            if (totalPages > 0 && page > totalPages)
            {
                page = totalPages;
            }

            int offset = (page - 1) * pageSize;
            args.Add("offset", offset);

            string sql = $@"
SELECT id, ip, oaid, image_base64, response_json, create_time
FROM promotion_query_samples
{where}
ORDER BY create_time DESC
LIMIT @offset, @count";

            var list = (await conn.QueryAsync<PromotionQuerySampleDTO>(sql, args)).ToList();
            return new PromotionQuerySampleQueryResultDTO
            {
                page = totalCount > 0 ? page : 1,
                pageSize = pageSize,
                totalCount = totalCount,
                logs = list.Select(ToLogDto).ToList()
            };
        }

        private static PromotionQuerySampleLogDTO ToLogDto(PromotionQuerySampleDTO item)
        {
            return new PromotionQuerySampleLogDTO
            {
                id = item.id,
                fileName = $"{item.id}.log",
                writeTime = item.create_time.ToString("yyyy-MM-dd HH:mm:ss.fff"),
                content = BuildLegacyContent(item),
                ip = item.ip,
                oaid = item.oaid,
                imageBase64 = item.image_base64,
                responseJson = item.response_json
            };
        }

        private static string BuildLegacyContent(PromotionQuerySampleDTO item)
        {
            var builder = new StringBuilder();
            if (!string.IsNullOrWhiteSpace(item.image_base64))
            {
                string clientPost = JsonSerializer.Serialize(new
                {
                    img = item.image_base64,
                    ip = item.ip,
                    oaid = item.oaid
                }, JsonOptions);
                AppendSection(builder, item.create_time, "clientPost", clientPost);
            }
            else if (!string.IsNullOrWhiteSpace(item.ip) || !string.IsNullOrWhiteSpace(item.oaid))
            {
                string clientPost = JsonSerializer.Serialize(new
                {
                    ip = item.ip,
                    oaid = item.oaid
                }, JsonOptions);
                AppendSection(builder, item.create_time, "clientPost", clientPost);
            }

            if (!string.IsNullOrWhiteSpace(item.response_json))
            {
                AppendSection(builder, item.create_time, "responseJson", item.response_json.Trim());
            }

            return builder.ToString().TrimEnd();
        }

        private static void AppendSection(StringBuilder builder, DateTime timestamp, string name, string content)
        {
            if (string.IsNullOrWhiteSpace(content))
            {
                return;
            }

            builder.Append(timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff"))
                .Append('\t')
                .Append(name)
                .AppendLine()
                .AppendLine(content);
        }

        private static string NormalizeKeyword(string keyword)
        {
            keyword = (keyword ?? string.Empty).Trim();
            if (keyword.EndsWith(".log", StringComparison.OrdinalIgnoreCase))
            {
                keyword = keyword[..^4];
            }
            return keyword;
        }

        private static bool TryConsumeQuota()
        {
            int remaining = GetRemainingCount();
            if (remaining <= 0)
            {
                return false;
            }

            long after = RedisHelper.IncrBy(SamplingRemainingKey, -1);
            RedisHelper.Expire(SamplingRemainingKey, SamplingSwitchExpireSeconds);
            if (after < 0)
            {
                RedisHelper.Set(SamplingRemainingKey, 0, SamplingSwitchExpireSeconds);
                return false;
            }

            return true;
        }

        private static string BuildSampleId()
        {
            var now = DateTime.Now;
            return $"{now:yyyyMMdd_HHmmss_fff}_{Guid.NewGuid():N}_{now:yyyyMMdd}";
        }

        private static async Task EnsureTableAsync()
        {
            if (_tableEnsured)
            {
                return;
            }

            await EnsureTableLock.WaitAsync();
            try
            {
                if (_tableEnsured)
                {
                    return;
                }

                const string sql = @"
CREATE TABLE IF NOT EXISTS promotion_query_samples (
    id VARCHAR(80) NOT NULL PRIMARY KEY COMMENT '采样日志ID',
    ip VARCHAR(64) NOT NULL DEFAULT '' COMMENT '请求IP',
    oaid VARCHAR(128) NOT NULL DEFAULT '' COMMENT '请求OAID',
    image_base64 LONGTEXT NOT NULL COMMENT '入参图片base64',
    response_json LONGTEXT NOT NULL COMMENT '返回JSON',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    KEY idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PromotionQuery采样记录';";

                using var conn = DBContext.GetOpenConnection();
                await conn.ExecuteAsync(sql);
                _tableEnsured = true;
            }
            finally
            {
                EnsureTableLock.Release();
            }
        }
    }
}
