using System.IO.Compression;
using System.Reflection;
using System.Text.Json;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using YMhut.Box.Core.App;
using YMhut.Box.Core.Feedback;
using YMhut.Box.Core.Logging;
using YMhut.Box.Core.System;
using YMhut.Box.Core.Tools;

namespace YMhut.Box.Tests;

[TestClass]
public sealed class FeedbackServiceTests
{
    [TestMethod]
    public void FeedbackCodeCreatesServerCompatibleCode()
    {
        var code = FeedbackCode.Create(DateTimeOffset.Parse("2026-06-04T10:00:00+08:00"));

        Assert.IsTrue(Regex.IsMatch(code, "^FB-20260604-[A-F0-9]{6}$"));
        Assert.AreEqual("FB-20260604-ABC123", FeedbackCode.Normalize(" fb-20260604-abc123 "));
        Assert.IsTrue(FeedbackCode.IsValid("fb-20260604-abc123"));
        Assert.IsFalse(FeedbackCode.IsValid("FB-20260604-XYZ123"));
    }

    [TestMethod]
    public async Task FeedbackPackageIncludesSelectedAttachmentsAndLocalizedLogs()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), "ymhut-box-feedback-tests", Guid.NewGuid().ToString("N"));
        var paths = AppPaths.ForCurrentUser(tempRoot);
        var logs = new JsonFileLogService(paths);
        var service = new FeedbackPackageService(logs, new FakeSystemMetricsService(), new ToolCatalog());

        await logs.WriteAsync("Information", "navigation", "Open toolbox", "https://example.com/private?token=secret");

        var package = await service.BuildPackageAsync(new FeedbackRequest(
            "同步状态卡住",
            "issue",
            "major",
            "dev@example.com",
            "首页一直显示后台校验中。",
            IncludeTodayLogs: true,
            IncludeToolStatus: true,
            IncludeSystemSummary: true,
            Language: "zh-CN",
            FeedbackCode: "FB-20260604-ABC123"));

        try
        {
            Assert.IsTrue(File.Exists(package.PackagePath));
            Assert.IsGreaterThan(0, package.PackageBytes);
            Assert.AreEqual(
                Path.GetFullPath(FeedbackPackageService.FeedbackPackagesRoot()),
                Path.GetFullPath(Path.GetDirectoryName(package.PackagePath)!));
            Assert.IsTrue(package.IncludedFiles.ContainsKey("feedback.json"));
            Assert.IsTrue(package.IncludedFiles.ContainsKey("tool-status.json"));
            Assert.IsTrue(package.IncludedFiles.ContainsKey("system-summary.json"));
            Assert.IsTrue(package.IncludedFiles.Keys.Any(name => name.StartsWith("logs-", StringComparison.OrdinalIgnoreCase)));
            Assert.IsTrue(package.SummaryText.Contains("首页一直显示后台校验中。", StringComparison.Ordinal));
            Assert.IsTrue(package.SummaryText.Contains("FB-20260604-ABC123", StringComparison.Ordinal));

            using var archive = ZipFile.OpenRead(package.PackagePath);
            Assert.IsNotNull(archive.GetEntry("feedback.json"));
            Assert.IsNotNull(archive.GetEntry("summary.txt"));
            var feedbackJson = ReadEntry(archive.GetEntry("feedback.json")!);
            using var feedbackDocument = JsonDocument.Parse(feedbackJson);
            Assert.AreEqual("FB-20260604-ABC123", feedbackDocument.RootElement.GetProperty("feedbackCode").GetString());
            Assert.AreEqual("FB-20260604-ABC123", feedbackDocument.RootElement.GetProperty("request").GetProperty("feedbackCode").GetString());

            var logsEntry = archive.Entries.Single(entry => entry.FullName.StartsWith("logs-", StringComparison.OrdinalIgnoreCase));
            var logsJson = ReadEntry(logsEntry);
            using var logsDocument = JsonDocument.Parse(logsJson);
            var firstLog = logsDocument.RootElement[0];
            Assert.AreEqual("导航", firstLog.GetProperty("display").GetProperty("category").GetString());
            Assert.AreEqual("打开工具箱", firstLog.GetProperty("display").GetProperty("message").GetString());
            Assert.AreEqual("远程服务", firstLog.GetProperty("display").GetProperty("detail").GetString());
            Assert.AreEqual("远程服务", firstLog.GetProperty("raw").GetProperty("detail").GetString());
            Assert.IsFalse(logsJson.Contains("example.com/private", StringComparison.OrdinalIgnoreCase));

            var toolStatus = ReadEntry(archive.GetEntry("tool-status.json")!);
            StringAssert.Contains(toolStatus, "\"id\": \"compression_codec\"");
            StringAssert.Contains(toolStatus, "\"exists\": true");
        }
        finally
        {
            DeleteIfExists(package.PackagePath);
            Directory.Delete(tempRoot, recursive: true);
        }
    }

    [TestMethod]
    public async Task FeedbackPackageCryptoRoundTripsLocalZip()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), "ymhut-box-feedback-tests", Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(tempRoot);
        var sourceZip = Path.Combine(tempRoot, "feedback.zip");
        var decryptedZip = Path.Combine(tempRoot, "feedback-decrypted.zip");

        try
        {
            using (var archive = ZipFile.Open(sourceZip, ZipArchiveMode.Create))
            {
                var entry = archive.CreateEntry("summary.txt");
                await using var stream = entry.Open();
                await using var writer = new StreamWriter(stream);
                await writer.WriteAsync("反馈包加密测试");
            }

            var encrypted = await FeedbackPackageCrypto.EncryptPackageAsync(sourceZip);
            Assert.IsTrue(File.Exists(encrypted.PackagePath));
            Assert.AreEqual(".ymfb", Path.GetExtension(encrypted.PackagePath));
            Assert.IsGreaterThan(new FileInfo(sourceZip).Length, encrypted.PackageBytes);

            await FeedbackPackageCrypto.DecryptPackageAsync(encrypted.PackagePath, decryptedZip);

            CollectionAssert.AreEqual(
                await File.ReadAllBytesAsync(sourceZip),
                await File.ReadAllBytesAsync(decryptedZip));
        }
        finally
        {
            if (Directory.Exists(tempRoot))
            {
                Directory.Delete(tempRoot, recursive: true);
            }
        }
    }

    [TestMethod]
    public async Task FeedbackPackageRejectsScreenshotOverFiveMegabytes()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), "ymhut-box-feedback-tests", Guid.NewGuid().ToString("N"));
        var paths = AppPaths.ForCurrentUser(tempRoot);
        var logs = new JsonFileLogService(paths);
        var service = new FeedbackPackageService(logs, new FakeSystemMetricsService(), new ToolCatalog());
        var screenshot = Path.Combine(tempRoot, "large.png");
        await File.WriteAllBytesAsync(screenshot, new byte[FeedbackPackageService.MaxScreenshotBytes + 1]);

        try
        {
            await Assert.ThrowsExactlyAsync<InvalidOperationException>(() =>
                service.BuildPackageAsync(new FeedbackRequest(
                    "截图过大",
                    "issue",
                    "normal",
                    string.Empty,
                    "测试截图限制。",
                    IncludeTodayLogs: false,
                    IncludeToolStatus: false,
                    IncludeSystemSummary: false,
                    ScreenshotPath: screenshot)));
        }
        finally
        {
            Directory.Delete(tempRoot, recursive: true);
        }
    }

    [TestMethod]
    public void FeedbackSubmissionSignatureIsStable()
    {
        var signature = FeedbackSubmissionService.Sign("1760000000", "abc123", new string('a', 64), "{\"ok\":true}");

        Assert.AreEqual(64, signature.Length);
        Assert.IsTrue(signature.All(character => Uri.IsHexDigit(character) && !char.IsUpper(character)));
        Assert.AreEqual(signature, FeedbackSubmissionService.Sign("1760000000", "abc123", new string('a', 64), "{\"ok\":true}"));
    }

    [TestMethod]
    public void FeedbackStatusResponseParsesExtendedServerFields()
    {
        var response = ParseStatusResponse("""
            {
              "ok": true,
              "code": "FB-20260604-ABC123",
              "status": "investigating",
              "statusLabel": "处理中",
              "statusDetail": "已收到日志，正在定位。",
              "category": "issue",
              "priority": "major",
              "hasReply": true,
              "reply": "请先保留现场文件。",
              "receivedAt": "2026-06-04T10:00:00+08:00",
              "updatedAt": "2026-06-04T10:30:00+08:00",
              "mailSent": true
            }
            """);

        Assert.IsTrue(response.Ok);
        Assert.AreEqual("FB-20260604-ABC123", response.Code);
        Assert.AreEqual("investigating", response.Status);
        Assert.AreEqual("处理中", response.StatusLabel);
        Assert.AreEqual("已收到日志，正在定位。", response.StatusDetail);
        Assert.AreEqual("issue", response.Category);
        Assert.AreEqual("major", response.Priority);
        Assert.IsTrue(response.HasReply);
        Assert.AreEqual("请先保留现场文件。", response.Reply);
        Assert.AreEqual("2026-06-04T10:30:00+08:00", response.UpdatedAt);
        Assert.IsTrue(response.MailSent);
    }

    [TestMethod]
    public async Task FeedbackRecordStoreReadsOldRecordsWithoutExtendedFields()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), "ymhut-box-feedback-record-tests", Guid.NewGuid().ToString("N"));
        var storagePath = Path.Combine(tempRoot, "feedback-records.json");
        Directory.CreateDirectory(tempRoot);
        await File.WriteAllTextAsync(storagePath, """
            [
              {
                "code": "FB-20260604-ABC123",
                "title": "Old record",
                "type": "issue",
                "severity": "normal",
                "createdAtUtc": "2026-06-04T02:00:00+00:00",
                "updatedAtUtc": "2026-06-04T02:10:00+00:00",
                "state": "Sent",
                "statusLabel": "Sent",
                "lastMessage": "The service received it."
              }
            ]
            """);

        try
        {
            var record = await new FeedbackRecordStore(storagePath).FindAsync("fb-20260604-abc123");

            Assert.IsNotNull(record);
            Assert.AreEqual("FB-20260604-ABC123", record.Code);
            Assert.AreEqual("Sent", record.StatusLabel);
            Assert.IsNull(record.ServiceStatusDetail);
            Assert.IsNull(record.ServiceCategory);
            Assert.IsNull(record.ServicePriority);
            Assert.IsNull(record.ServiceMailSent);
        }
        finally
        {
            if (Directory.Exists(tempRoot))
            {
                Directory.Delete(tempRoot, recursive: true);
            }
        }
    }

    [TestMethod]
    public async Task FeedbackRecordStoreKeepsStatusMessageAndPublicReplySeparate()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), "ymhut-box-feedback-record-tests", Guid.NewGuid().ToString("N"));
        var storagePath = Path.Combine(tempRoot, "feedback-records.json");
        var store = new FeedbackRecordStore(storagePath);
        var now = DateTimeOffset.Parse("2026-06-04T03:00:00Z");

        try
        {
            await store.UpsertAsync(new FeedbackRecord(
                "FB-20260604-ABC123",
                "Status query",
                "issue",
                "major",
                now,
                now,
                FeedbackRecordState.StatusUpdated,
                "处理中",
                "已收到日志，正在定位。",
                LastQueryAtUtc: now,
                ServiceStatus: "investigating",
                PublicReply: "请先保留现场文件。",
                ServiceStatusDetail: "已收到日志，正在定位。",
                ServiceCategory: "issue",
                ServicePriority: "major",
                ServiceMailSent: true,
                ServiceReceivedAt: "2026-06-04T10:00:00+08:00",
                ServiceUpdatedAt: "2026-06-04T10:30:00+08:00"));

            var record = await store.FindAsync("FB-20260604-ABC123");

            Assert.IsNotNull(record);
            Assert.AreEqual("已收到日志，正在定位。", record.LastMessage);
            Assert.IsFalse(record.LastMessage.Contains("请先保留现场文件。", StringComparison.Ordinal));
            Assert.AreEqual("请先保留现场文件。", record.PublicReply);
            Assert.AreEqual("investigating", record.ServiceStatus);
            Assert.AreEqual("issue", record.ServiceCategory);
            Assert.AreEqual("major", record.ServicePriority);
            Assert.IsTrue(record.ServiceMailSent);
            Assert.AreEqual("2026-06-04T10:30:00+08:00", record.ServiceUpdatedAt);
        }
        finally
        {
            if (Directory.Exists(tempRoot))
            {
                Directory.Delete(tempRoot, recursive: true);
            }
        }
    }

    [TestMethod]
    public async Task FeedbackRecordStoreArchivesOldRecordsAndKeepsThemQueryable()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), "ymhut-box-feedback-record-tests", Guid.NewGuid().ToString("N"));
        var storagePath = Path.Combine(tempRoot, "feedback-records.json");
        var store = new FeedbackRecordStore(storagePath);
        var now = DateTimeOffset.Parse("2026-06-05T12:00:00Z");
        var oldCreated = now.AddDays(-11);

        try
        {
            await store.UpsertAsync(new FeedbackRecord(
                "FB-20260525-ABC123",
                "旧反馈",
                "issue",
                "normal",
                oldCreated,
                oldCreated,
                FeedbackRecordState.Sent,
                "发送成功",
                "服务端已接收。",
                PackagePath: Path.Combine(tempRoot, "feedback.zip"),
                PackageSha256: new string('a', 64),
                PackageBytes: 128,
                SubmittedAtUtc: oldCreated));

            var archived = await store.ArchiveOlderThanAsync(TimeSpan.FromDays(10), now);
            var record = archived.Single();
            Assert.IsTrue(record.IsArchived);
            Assert.AreEqual(FeedbackRecordState.Archived, record.State);
            Assert.AreEqual("已归档", record.StatusLabel);
            Assert.IsNotNull(record.ArchivedAtUtc);

            var updated = await store.UpsertAsync(record with
            {
                State = FeedbackRecordState.StatusUpdated,
                StatusLabel = "状态已更新",
                LastMessage = "当前状态：已处理。",
                UpdatedAtUtc = now.AddMinutes(5),
                LastQueryAtUtc = now.AddMinutes(5)
            });

            Assert.IsTrue(updated.IsArchived);
            Assert.AreEqual(FeedbackRecordState.StatusUpdated, updated.State);
            Assert.AreEqual("状态已更新", updated.StatusLabel);
            Assert.AreEqual("当前状态：已处理。", updated.LastMessage);

            var reloaded = await new FeedbackRecordStore(storagePath).FindAsync("fb-20260525-abc123");
            Assert.IsNotNull(reloaded);
            Assert.IsTrue(reloaded.IsArchived);
            Assert.AreEqual("FB-20260525-ABC123", reloaded.Code);
        }
        finally
        {
            if (Directory.Exists(tempRoot))
            {
                Directory.Delete(tempRoot, recursive: true);
            }
        }
    }

    private static string ReadEntry(ZipArchiveEntry entry)
    {
        using var stream = entry.Open();
        using var reader = new StreamReader(stream);
        return reader.ReadToEnd();
    }

    private static FeedbackStatusResponse ParseStatusResponse(string json)
    {
        var method = typeof(FeedbackSubmissionService).GetMethod("ParseStatusResponse", BindingFlags.NonPublic | BindingFlags.Static);
        Assert.IsNotNull(method);
        return (FeedbackStatusResponse)method.Invoke(null, new object?[] { json })!;
    }

    private static void DeleteIfExists(string path)
    {
        if (File.Exists(path))
        {
            File.Delete(path);
        }
    }

    private sealed class FakeSystemMetricsService : ISystemMetricsService
    {
        public SystemMetricsSnapshot Capture()
        {
            return new SystemMetricsSnapshot(
                "TEST-PC",
                "Windows Test",
                8,
                1024,
                2048,
                42.5,
                16UL * 1024 * 1024 * 1024,
                8UL * 1024 * 1024 * 1024,
                8UL * 1024 * 1024 * 1024,
                50,
                1.2,
                TimeSpan.FromMinutes(12),
                24,
                512,
                256UL * 1024 * 1024 * 1024,
                128UL * 1024 * 1024 * 1024,
                128UL * 1024 * 1024 * 1024,
                50,
                true,
                ".NET 10.0",
                DateTimeOffset.Parse("2026-06-04T10:00:00+08:00"));
        }
    }
}
