using System.Text;

namespace NpncbDiagnostic;

internal static class ReportFormatter
{
    private const int MaximumMessageCharacters = 4_096;
    private const int MaximumHexPreviewBytes = 128;
    private const int MaximumDetailedNpncbRows = 500;

    public static string BuildText(ZipDiagnosticReport report, bool includeDetails)
    {
        if (report is null) throw new ArgumentNullException(nameof(report));

        var builder = new StringBuilder();
        builder.AppendLine("NPNCB DBA 취약점 진단 보고서");
        builder.AppendLine(new string('=', 72));
        builder.AppendLine($"판정: {GetVerdictLabel(report.Verdict)}");
        builder.AppendLine($"요약: {report.Summary}");
        builder.AppendLine($"ZIP: {report.ZipPath}");
        builder.AppendLine($"파일 크기: {report.ZipFileSize:N0} bytes");
        builder.AppendLine($"검사 시각(UTC): {report.ScannedAtUtc:yyyy-MM-dd HH:mm:ss}Z");
        builder.AppendLine();

        builder.AppendLine("전체 통계");
        builder.AppendLine($"- ZIP 엔트리: {report.Entries.Count:N0}");
        builder.AppendLine($"- 검사한 DEFLATE 엔트리: {report.ScannedDeflateEntryCount:N0}");
        builder.AppendLine($"- 물리적 DEFLATE 블록: {report.TotalPhysicalBlockCount:N0}");
        builder.AppendLine($"- NPNCB: {report.TotalNpncbCount:N0}");
        builder.AppendLine($"- 연결 DBA 용량: {report.TotalDbaBitCount:N0} bits " +
                           $"({report.TotalDbaBitCount / 8:N0} complete bytes + " +
                           $"{report.TotalDbaBitCount % 8} bits)");
        builder.AppendLine($"- 0이 아닌 DBA 비트: {report.TotalNonZeroDbaBitCount:N0}");
        builder.AppendLine($"- 높은 신뢰도 메시지 후보: {report.LikelyMessageCount:N0}");
        builder.AppendLine($"- 낮은 신뢰도 메시지 후보: {report.PossibleMessageCount:N0}");
        builder.AppendLine($"- 의심 바이너리 DBA: {report.SuspiciousBinaryCount:N0}");
        builder.AppendLine($"- 미완료/오류 엔트리: " +
                           $"{report.InaccessibleDeflateEntryCount + report.InvalidEntryCount:N0}");
        builder.AppendLine();

        foreach (ZipEntryDiagnostic entry in report.Entries)
            AppendEntry(builder, entry, includeDetails);

        builder.AppendLine("판정 해석");
        builder.AppendLine("- CLEAN: 분석 가능한 DEFLATE 엔트리에 NPNCB가 없습니다.");
        builder.AppendLine("- INFORMATIONAL: NPNCB는 있지만 DBA가 모두 0입니다.");
        builder.AppendLine("- SUSPICIOUS: DBA에 0이 아닌 비트가 있으나 메시지로 확정하기 어렵습니다.");
        builder.AppendLine("- WARNING: 이 PoC 형식과 일치하는 UTF-8 메시지 후보가 복원되었습니다.");
        builder.AppendLine("- INCONCLUSIVE: 암호화, 크기 제한 또는 구조 오류로 일부를 검사하지 못했습니다.");
        builder.AppendLine();
        builder.AppendLine(
            "주의: NPNCB와 00 00 FF FF의 존재만으로 악성을 확정할 수 없습니다. " +
            "반대로 CLEAN 판정도 ZIP에 다른 악성 콘텐츠나 다른 은닉 채널이 없음을 보증하지 않습니다.");

        return builder.ToString();
    }

    private static void AppendEntry(
        StringBuilder builder,
        ZipEntryDiagnostic entry,
        bool includeDetails)
    {
        builder.AppendLine(new string('-', 72));
        builder.AppendLine($"엔트리 #{entry.Index}: {EscapeForDisplay(entry.Name, 1_024)}");
        builder.AppendLine($"상태: {GetEntryStateLabel(entry.State)}");
        builder.AppendLine($"방식/플래그: {GetMethodLabel(entry.CompressionMethod)} / 0x{entry.GeneralPurposeFlags:X4}");
        builder.AppendLine($"압축/원본 크기: {entry.CompressedSize:N0} / {entry.UncompressedSize:N0} bytes");
        builder.AppendLine($"기대 CRC-32: 0x{entry.ExpectedCrc32:X8}");

        if (!string.IsNullOrWhiteSpace(entry.Note))
            builder.AppendLine($"설명: {EscapeForDisplay(entry.Note, 2_048)}");

        DeflateScanResult? deflate = entry.Deflate;
        if (deflate is null)
        {
            builder.AppendLine();
            return;
        }

        builder.AppendLine(
            "블록: " +
            $"total={deflate.PhysicalBlockCount:N0}, " +
            $"stored-data={deflate.StoredDataBlockCount:N0}, " +
            $"fixed={deflate.FixedBlockCount:N0}, " +
            $"dynamic={deflate.DynamicBlockCount:N0}, " +
            $"NPNCB={deflate.NpncbCount:N0}");
        builder.AppendLine(
            $"DBA: {deflate.DbaBitCount:N0} bits, " +
            $"non-zero={deflate.NonZeroDbaBitCount:N0} bits");
        builder.AppendLine(
            $"복호화 검증: {deflate.DecodedByteCount:N0} bytes, CRC-32=0x{deflate.ComputedCrc32:X8}");

        if (deflate.FinalTrailingBitCount > 0)
        {
            builder.AppendLine(
                $"최종 블록 뒤 미사용 비트: {deflate.FinalTrailingBitCount} " +
                $"(non-zero={deflate.NonZeroFinalTrailingBitCount})");
        }

        DbaMessageFinding? finding = entry.MessageFinding;
        if (finding is not null)
        {
            builder.AppendLine($"DBA 판정: {GetFindingLabel(finding.Kind)}");
            builder.AppendLine($"근거: {EscapeForDisplay(finding.Explanation, 2_048)}");
            builder.AppendLine(
                $"완전한 DBA 바이트/종료자: {finding.WholeDbaByteCount:N0} / " +
                $"{(finding.TerminatorFound ? "found" : "not found")}");
            builder.AppendLine(
                $"UTF-8/가독 문자 비율: {(finding.IsValidUtf8 ? "valid" : "invalid")} / " +
                $"{finding.ReadableCharacterRatio:P1}");

            if (finding.Message is not null && finding.CandidateBytes.Length > 0)
            {
                builder.AppendLine(
                    $"복원 메시지: \"{EscapeForDisplay(finding.Message, MaximumMessageCharacters)}\"");
            }

            if (finding.ReconstructedWholeBytes.Length > 0)
            {
                builder.AppendLine(
                    $"DBA bytes(hex): {ToHexPreview(finding.ReconstructedWholeBytes, MaximumHexPreviewBytes)}");
            }

            if (finding.NonZeroBitsAfterTerminator > 0)
                builder.AppendLine($"종료자 뒤 0이 아닌 비트: {finding.NonZeroBitsAfterTerminator:N0}");
        }

        if (includeDetails && deflate.NpncbBlocks.Count > 0)
            AppendNpncbDetails(builder, deflate.NpncbBlocks);

        builder.AppendLine();
    }

    private static void AppendNpncbDetails(
        StringBuilder builder,
        IReadOnlyList<NpncbBlockFinding> blocks)
    {
        builder.AppendLine();
        builder.AppendLine("NPNCB 상세");
        builder.AppendLine("No.  Block  Final  BlockStart  DBAStart  Bits  DBA-value  NonZero");

        int rowCount = Math.Min(blocks.Count, MaximumDetailedNpncbRows);
        for (int index = 0; index < rowCount; index++)
        {
            NpncbBlockFinding block = blocks[index];
            builder.AppendLine(
                $"{index,3}  {block.PhysicalBlockIndex,5}  " +
                $"{(block.IsFinal ? "yes" : "no"),5}  " +
                $"{block.BlockStartBit,10}  {block.DbaStartBit,8}  " +
                $"{block.DbaBitCount,4}  0x{block.DbaValue:X2}       {block.NonZeroBitCount,3}");
        }

        if (rowCount < blocks.Count)
            builder.AppendLine($"... {blocks.Count - rowCount:N0} rows omitted");
    }

    private static string ToHexPreview(byte[] bytes, int maximumBytes)
    {
        int count = Math.Min(bytes.Length, maximumBytes);
        string hex = Convert.ToHexString(bytes.AsSpan(0, count));
        return count < bytes.Length ? hex + $"...(+{bytes.Length - count:N0} bytes)" : hex;
    }

    private static string EscapeForDisplay(string text, int maximumCharacters)
    {
        if (text is null) return string.Empty;

        var builder = new StringBuilder(Math.Min(text.Length, maximumCharacters));
        int processed = 0;

        foreach (char value in text)
        {
            if (processed >= maximumCharacters)
            {
                builder.Append("…");
                break;
            }

            switch (value)
            {
                case '\r': builder.Append("\\r"); break;
                case '\n': builder.Append("\\n"); break;
                case '\t': builder.Append("\\t"); break;
                default:
                    if (char.IsControl(value)) builder.Append($"\\u{(int)value:X4}");
                    else builder.Append(value);
                    break;
            }

            processed++;
        }

        return builder.ToString();
    }

    public static string GetVerdictLabel(DiagnosticVerdict verdict) => verdict switch
    {
        DiagnosticVerdict.Clean => "CLEAN — NPNCB DBA 은닉 흔적 없음",
        DiagnosticVerdict.Informational => "INFORMATIONAL — NPNCB 존재, DBA는 0",
        DiagnosticVerdict.Inconclusive => "INCONCLUSIVE — 일부 검사 불가",
        DiagnosticVerdict.Suspicious => "SUSPICIOUS — 0이 아닌 DBA 흔적",
        DiagnosticVerdict.Warning => "WARNING — 숨겨진 메시지 후보 발견",
        _ => verdict.ToString()
    };

    private static string GetEntryStateLabel(EntryScanState state) => state switch
    {
        EntryScanState.Scanned => "scanned",
        EntryScanState.SkippedDirectory => "skipped-directory",
        EntryScanState.SkippedNonDeflate => "skipped-non-deflate",
        EntryScanState.SkippedEncrypted => "skipped-encrypted",
        EntryScanState.SkippedLimit => "skipped-safety-limit",
        EntryScanState.Invalid => "invalid-or-corrupt",
        _ => state.ToString()
    };

    private static string GetFindingLabel(MessageFindingKind kind) => kind switch
    {
        MessageFindingKind.None => "NPNCB DBA 없음",
        MessageFindingKind.ZeroPaddingOnly => "DBA zero padding only",
        MessageFindingKind.LikelyTextMessage => "높은 신뢰도 UTF-8 메시지 후보",
        MessageFindingKind.PossibleTextMessage => "낮은 신뢰도 UTF-8 메시지 후보",
        MessageFindingKind.SuspiciousBinary => "의심 바이너리 DBA",
        _ => kind.ToString()
    };

    private static string GetMethodLabel(ushort method) => method switch
    {
        0 => "Stored (0)",
        8 => "DEFLATE (8)",
        12 => "BZIP2 (12)",
        14 => "LZMA (14)",
        93 => "Zstandard (93)",
        99 => "AES (99)",
        _ => $"method {method}"
    };
}
