using System.Text;
using System.Windows.Forms;

namespace NpncbDiagnostic;

internal static class Program
{
    [STAThread]
    private static int Main(string[] args)
    {
        Console.InputEncoding = Encoding.UTF8;
        Console.OutputEncoding = Encoding.UTF8;
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        bool interactive = args.Length == 0;

        try
        {
            CommandLineOptions commandLine = CommandLineOptions.Parse(args);
            if (commandLine.ShowHelp)
            {
                PrintUsage();
                return 0;
            }

            string? zipPath = commandLine.ZipPath;
            if (zipPath is null)
            {
                zipPath = SelectZipFile();
                if (zipPath is null) return 0;
            }

            Console.WriteLine("ZIP의 Central Directory와 raw DEFLATE 블록을 분석합니다...");
            ZipDiagnosticReport report = ZipArchiveAnalyzer.Analyze(zipPath);
            string reportText = ReportFormatter.BuildText(
                report,
                commandLine.IncludeDetails || interactive);

            WriteReportToConsole(report, reportText);

            if (!string.IsNullOrWhiteSpace(commandLine.ReportPath))
            {
                SaveTextReport(commandLine.ReportPath, reportText);
            }
            else if (interactive && AskYesNo("진단 보고서를 파일로 저장할까요? [Y/n]: ", true))
            {
                string? reportPath = SelectReportPath(zipPath);
                if (reportPath is not null) SaveTextReport(reportPath, reportText);
            }

            if (interactive)
            {
                Console.WriteLine();
                Console.Write("종료하려면 Enter를 누르세요...");
                Console.ReadLine();
            }

            return report.Verdict switch
            {
                DiagnosticVerdict.Warning or DiagnosticVerdict.Suspicious => 1,
                DiagnosticVerdict.Inconclusive => 2,
                _ => 0
            };
        }
        catch (Exception exception)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.Error.WriteLine();
            Console.Error.WriteLine("진단 실패:");
            Console.ResetColor();
            Console.Error.WriteLine(exception);

            if (interactive)
            {
                Console.WriteLine();
                Console.Write("종료하려면 Enter를 누르세요...");
                Console.ReadLine();
            }

            return 2;
        }
    }

    private static string? SelectZipFile()
    {
        using var dialog = new OpenFileDialog
        {
            Title = "NPNCB DBA 취약점을 진단할 ZIP 파일을 선택하세요",
            Filter = "ZIP 파일 (*.zip)|*.zip|모든 파일 (*.*)|*.*",
            CheckFileExists = true,
            Multiselect = false,
            RestoreDirectory = true
        };

        return dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : null;
    }

    private static string? SelectReportPath(string zipPath)
    {
        using var dialog = new SaveFileDialog
        {
            Title = "NPNCB 진단 보고서 저장",
            Filter = "텍스트 보고서 (*.txt)|*.txt|모든 파일 (*.*)|*.*",
            DefaultExt = "txt",
            AddExtension = true,
            FileName = Path.GetFileNameWithoutExtension(zipPath) + ".npncb-diagnostic.txt",
            InitialDirectory = Path.GetDirectoryName(zipPath) ?? string.Empty,
            OverwritePrompt = true,
            RestoreDirectory = true
        };

        return dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : null;
    }

    private static void SaveTextReport(string reportPath, string reportText)
    {
        string fullPath = Path.GetFullPath(reportPath);
        string? directory = Path.GetDirectoryName(fullPath);
        if (!string.IsNullOrEmpty(directory)) Directory.CreateDirectory(directory);
        File.WriteAllText(fullPath, reportText, new UTF8Encoding(false));
        Console.WriteLine($"보고서 저장: {fullPath}");
    }

    private static void WriteReportToConsole(
        ZipDiagnosticReport report,
        string reportText)
    {
        ConsoleColor color = report.Verdict switch
        {
            DiagnosticVerdict.Warning => ConsoleColor.Red,
            DiagnosticVerdict.Suspicious => ConsoleColor.Yellow,
            DiagnosticVerdict.Inconclusive => ConsoleColor.DarkYellow,
            DiagnosticVerdict.Informational => ConsoleColor.Cyan,
            _ => ConsoleColor.Green
        };

        Console.WriteLine();
        Console.ForegroundColor = color;
        Console.WriteLine(ReportFormatter.GetVerdictLabel(report.Verdict));
        Console.ResetColor();
        Console.WriteLine();
        Console.Write(reportText);
    }

    private static bool AskYesNo(string prompt, bool defaultValue)
    {
        Console.Write(prompt);
        string? answer = Console.ReadLine()?.Trim();
        if (string.IsNullOrEmpty(answer)) return defaultValue;

        if (answer.Equals("y", StringComparison.OrdinalIgnoreCase) ||
            answer.Equals("yes", StringComparison.OrdinalIgnoreCase) ||
            answer.Equals("예", StringComparison.OrdinalIgnoreCase))
        {
            return true;
        }

        if (answer.Equals("n", StringComparison.OrdinalIgnoreCase) ||
            answer.Equals("no", StringComparison.OrdinalIgnoreCase) ||
            answer.Equals("아니오", StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }

        return defaultValue;
    }

    private static void PrintUsage()
    {
        Console.WriteLine("NPNCB DBA 취약점 진단기");
        Console.WriteLine();
        Console.WriteLine("사용법:");
        Console.WriteLine("  NpncbDiagnostic.exe [archive.zip] [--details] [--report output.txt]");
        Console.WriteLine();
        Console.WriteLine("인수 없이 실행하면 ZIP 선택 대화상자가 열립니다.");
        Console.WriteLine();
        Console.WriteLine("종료 코드:");
        Console.WriteLine("  0  CLEAN 또는 DBA가 모두 0인 INFORMATIONAL");
        Console.WriteLine("  1  SUSPICIOUS 또는 WARNING");
        Console.WriteLine("  2  INCONCLUSIVE 또는 처리 오류");
    }
}

internal sealed record CommandLineOptions(
    string? ZipPath,
    string? ReportPath,
    bool IncludeDetails,
    bool ShowHelp)
{
    public static CommandLineOptions Parse(string[] args)
    {
        string? zipPath = null;
        string? reportPath = null;
        bool includeDetails = false;
        bool showHelp = false;

        for (int index = 0; index < args.Length; index++)
        {
            string argument = args[index];

            if (argument is "--help" or "-h" or "/?")
            {
                showHelp = true;
                continue;
            }

            if (argument.Equals("--details", StringComparison.OrdinalIgnoreCase))
            {
                includeDetails = true;
                continue;
            }

            if (argument.Equals("--report", StringComparison.OrdinalIgnoreCase))
            {
                if (++index >= args.Length)
                    throw new ArgumentException("--report 뒤에 출력 파일 경로가 필요합니다.");
                reportPath = args[index];
                continue;
            }

            if (argument.StartsWith("-", StringComparison.Ordinal))
                throw new ArgumentException($"알 수 없는 옵션: {argument}");

            if (zipPath is not null)
                throw new ArgumentException("ZIP 파일 경로는 하나만 지정할 수 있습니다.");

            zipPath = Path.GetFullPath(argument);
        }

        return new CommandLineOptions(zipPath, reportPath, includeDetails, showHelp);
    }
}
