using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using YMhut.Box.Core;
using YMhut.Box.Core.Logging;
using YMhut.Box.Core.System;
using YMhut.Box.Core.Tools;

namespace YMhut.Box.Core.Feedback;

public sealed class FeedbackPackageService(
    ILogService logService,
    ISystemMetricsService? systemMetricsService = null,
    ToolCatalog? toolCatalog = null) : IFeedbackPackageService
{
    public const long MaxScreenshotBytes = 5L * 1024L * 1024L;
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web) { WriteIndented = true };
    private static readonly string[] AllowedScreenshotExtensions = [".png", ".jpg", ".jpeg", ".webp", ".bmp"];

    public async Task<FeedbackPackageResult> BuildPackageAsync(
        FeedbackRequest request,
        CancellationToken cancellationToken = default)
    {
        ValidateRequest(request);
        var feedbackRoot = FeedbackPackagesRoot();
        Directory.CreateDirectory(feedbackRoot);
        var createdAt = DateTimeOffset.Now;
        var packagePath = NextAvailablePath(Path.Combine(feedbackRoot, $"feedback-{createdAt:yyyyMMdd-HHmmss}-{RandomSuffix()}.zip"));
        var included = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

        await using (var fileStream = File.Create(packagePath))
        using (var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, leaveOpen: false))
        {
            var manifest = new
            {
                createdAt,
                feedbackCode = FeedbackCode.Normalize(request.FeedbackCode),
                request = new
                {
                    feedbackCode = FeedbackCode.Normalize(request.FeedbackCode),
                    title = SensitiveText.Sanitize(request.Title, 160),
                    type = request.Type,
                    severity = request.Severity,
                    contact = SensitiveText.Sanitize(request.Contact, 180),
                    body = SensitiveText.Sanitize(request.Body, 4000),
                    includeTodayLogs = request.IncludeTodayLogs,
                    includeToolStatus = request.IncludeToolStatus,
                    includeSystemSummary = request.IncludeSystemSummary,
                    hasScreenshot = !string.IsNullOrWhiteSpace(request.ScreenshotPath)
                },
                client = new
                {
                    app = "YMhut Box"
                }
            };
            await WriteJsonEntryAsync(archive, "feedback.json", manifest, cancellationToken).ConfigureAwait(false);
            included["feedback.json"] = "反馈正文和勾选项";

            if (request.IncludeSystemSummary)
            {
                var system = CaptureSystemSummary();
                await WriteJsonEntryAsync(archive, "system-summary.json", system, cancellationToken).ConfigureAwait(false);
                included["system-summary.json"] = "系统摘要";
            }

            if (request.IncludeToolStatus)
            {
                var status = CaptureToolStatus();
                await WriteJsonEntryAsync(archive, "tool-status.json", status, cancellationToken).ConfigureAwait(false);
                included["tool-status.json"] = "工具运行状态";
            }

            if (request.IncludeTodayLogs)
            {
                var entries = await logService.ReadByDateAsync(
                    DateOnly.FromDateTime(DateTime.Now),
                    take: 1000,
                    cancellationToken: cancellationToken).ConfigureAwait(false);
                var logs = entries.Select(entry => new
                {
                    raw = new
                    {
                        timestamp = entry.Timestamp,
                        level = entry.Level,
                        category = entry.Category,
                        message = SensitiveText.Sanitize(entry.Message, 360),
                        detail = SensitiveText.Sanitize(entry.Detail, 800)
                    },
                    display = new
                    {
                        level = LogDisplayLocalizer.Level(entry.Level, request.Language),
                        category = LogDisplayLocalizer.Category(entry.Category, request.Language),
                        message = LogDisplayLocalizer.Message(entry.Message, request.Language),
                        detail = LogDisplayLocalizer.Detail(entry.Detail, request.Language, 800)
                    }
                }).ToArray();
                await WriteJsonEntryAsync(archive, $"logs-{createdAt:yyyyMMdd}.json", logs, cancellationToken).ConfigureAwait(false);
                included[$"logs-{createdAt:yyyyMMdd}.json"] = $"当天日志 {logs.Length} 条";
            }

            if (!string.IsNullOrWhiteSpace(request.ScreenshotPath))
            {
                var screenshot = ValidateScreenshot(request.ScreenshotPath);
                var entryName = "screenshot" + screenshot.Extension.ToLowerInvariant();
                var entry = archive.CreateEntry(entryName, CompressionLevel.Optimal);
                await using var input = File.OpenRead(screenshot.FullName);
                await using var output = entry.Open();
                await input.CopyToAsync(output, cancellationToken).ConfigureAwait(false);
                included[entryName] = $"截图 {screenshot.Length} bytes";
            }

            var summary = BuildSummary(request, included, createdAt);
            await WriteTextEntryAsync(archive, "summary.txt", summary, cancellationToken).ConfigureAwait(false);
            included["summary.txt"] = "反馈包摘要";
        }

        var sha256 = await HashFileAsync(packagePath, cancellationToken).ConfigureAwait(false);
        var fileInfo = new FileInfo(packagePath);
        return new FeedbackPackageResult(
            packagePath,
            sha256,
            fileInfo.Length,
            createdAt,
            BuildSummary(request, included, createdAt),
            included);
    }

    private static void ValidateRequest(FeedbackRequest request)
    {
        if (string.IsNullOrWhiteSpace(request.Body))
        {
            throw new InvalidOperationException("反馈正文不能为空。");
        }
    }

    public static string FeedbackPackagesRoot()
    {
        return Path.Combine(AppContext.BaseDirectory, "FeedbackPackages");
    }

    private static FileInfo ValidateScreenshot(string path)
    {
        var info = new FileInfo(path.Trim().Trim('"'));
        if (!info.Exists)
        {
            throw new FileNotFoundException("截图文件不存在。", info.FullName);
        }

        if (!AllowedScreenshotExtensions.Contains(info.Extension, StringComparer.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("截图仅支持 png、jpg、jpeg、webp、bmp。");
        }

        if (info.Length > MaxScreenshotBytes)
        {
            throw new InvalidOperationException("截图不能超过 5 MB。");
        }

        return info;
    }

    private object CaptureSystemSummary()
    {
        var snapshot = systemMetricsService?.Capture();
        return new
        {
            machineName = SensitiveText.Sanitize(snapshot?.MachineName, 120),
            osDescription = SensitiveText.Sanitize(snapshot?.OsDescription, 200),
            processorCount = snapshot?.ProcessorCount ?? Environment.ProcessorCount,
            workingSetBytes = snapshot?.WorkingSetBytes ?? Environment.WorkingSet,
            managedMemoryBytes = snapshot?.ManagedMemoryBytes ?? GC.GetTotalMemory(false),
            capturedAt = snapshot?.CapturedAt ?? DateTimeOffset.Now
        };
    }

    private object CaptureToolStatus()
    {
        var catalog = toolCatalog ?? new ToolCatalog();
        return new
        {
            capturedAt = DateTimeOffset.Now,
            totalTools = catalog.Modules.Count,
            byCategory = catalog.Modules
                .GroupBy(module => module.Metadata.Category.ToString())
                .OrderBy(group => group.Key, StringComparer.OrdinalIgnoreCase)
                .Select(group => new { category = group.Key, count = group.Count() })
                .ToArray(),
            newTools = new[]
            {
                "compression_codec",
                "totp_generator",
                "color_contrast_checker",
                "url_redirect_trace",
                "image_metadata_inspector",
                "markdown_preview"
            }.Select(id => new { id, exists = catalog.GetById(id) is not null }).ToArray()
        };
    }

    private static string BuildSummary(FeedbackRequest request, IReadOnlyDictionary<string, string> included, DateTimeOffset createdAt)
    {
        var english = string.Equals(request.Language, "en-US", StringComparison.OrdinalIgnoreCase);
        var lines = new List<string>
        {
            english ? "YMhut Box Feedback Package" : "YMhut Box 反馈包",
            $"{(english ? "Created" : "创建时间")}: {createdAt:O}",
            $"{(english ? "Feedback code" : "反馈编号")}: {FeedbackCode.Normalize(request.FeedbackCode)}",
            $"{(english ? "Title" : "标题")}: {SensitiveText.Sanitize(request.Title, 160)}",
            $"{(english ? "Type" : "类型")}: {request.Type}",
            $"{(english ? "Severity" : "严重程度")}: {request.Severity}",
            $"{(english ? "Contact" : "联系方式")}: {SensitiveText.Sanitize(request.Contact, 180)}",
            $"{(english ? "Body" : "正文")}: {SensitiveText.Sanitize(request.Body, 1200)}",
            string.Empty,
            english ? "Included files:" : "包含文件："
        };
        lines.AddRange(included.Select(pair => $"- {pair.Key}: {pair.Value}"));
        return string.Join(Environment.NewLine, lines);
    }

    private static async Task WriteJsonEntryAsync(ZipArchive archive, string name, object value, CancellationToken cancellationToken)
    {
        var json = JsonSerializer.Serialize(value, JsonOptions);
        await WriteTextEntryAsync(archive, name, json, cancellationToken).ConfigureAwait(false);
    }

    private static async Task WriteTextEntryAsync(ZipArchive archive, string name, string text, CancellationToken cancellationToken)
    {
        var entry = archive.CreateEntry(name, CompressionLevel.Optimal);
        await using var stream = entry.Open();
        await using var writer = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
        await writer.WriteAsync(text.AsMemory(), cancellationToken).ConfigureAwait(false);
    }

    private static async Task<string> HashFileAsync(string path, CancellationToken cancellationToken)
    {
        await using var stream = File.OpenRead(path);
        var hash = await SHA256.HashDataAsync(stream, cancellationToken).ConfigureAwait(false);
        return Convert.ToHexString(hash).ToLowerInvariant();
    }

    private static string RandomSuffix()
    {
        Span<byte> bytes = stackalloc byte[3];
        RandomNumberGenerator.Fill(bytes);
        return Convert.ToHexString(bytes).ToLowerInvariant();
    }

    private static string NextAvailablePath(string path)
    {
        if (!File.Exists(path))
        {
            return path;
        }

        var directory = Path.GetDirectoryName(path) ?? Environment.CurrentDirectory;
        var name = Path.GetFileNameWithoutExtension(path);
        var extension = Path.GetExtension(path);
        for (var index = 2; ; index++)
        {
            var candidate = Path.Combine(directory, $"{name}-{index}{extension}");
            if (!File.Exists(candidate))
            {
                return candidate;
            }
        }
    }
}
