using AyCode.Core.Compression;
using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Tests.Serialization;   // DrainFromAsync extension (test-only, used by benchmark)
using AyCode.Core.Tests.TestModels;
using MemoryPack;
#if !AYCODE_NATIVEAOT
using MessagePack;
using MessagePack.Resolvers;
#endif
using Microsoft.Extensions.Options;
using System.Buffers;
using System.Diagnostics;
using System.IO.Pipelines;
using System.IO.Pipes;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;

using AyCode.Core.Benchmarks.Workloads.Scenarios;

namespace AyCode.Core.Serializers.Console;

/// <summary>
/// Comprehensive benchmark application for all serializers.
/// Compares: AcBinary (all options), MemoryPack, MessagePack, Newtonsoft.Json, System.Text.Json
/// 
/// Usage:
///   dotnet run                    # Run all benchmarks
///   dotnet run -- quick           # Quick mode (fewer iterations)
///   dotnet run -- serialize       # Serialize only
///   dotnet run -- deserialize     # Deserialize only
/// </summary>
public static class Program
{
    // Configuration (constants, mutable state, attribute-flag aggregation) → Configuration.cs
    // BuildAcBinary + GetMemPack helpers → Benchmarks/BenchmarkOptions.cs

    public static void Main(string[] args)
    {
        // Set console encoding to UTF-8 for proper Unicode character display
        System.Console.OutputEncoding = Encoding.UTF8;

        // Setup validation — abort BEFORE any benchmark logic if MemoryPack baseline is invalid.
        // Done early so user is told immediately, not after warmup.
        BenchmarkLoop.ValidateMemoryPackSetup();

        // CLI mode (args provided): run once, parse args, exit. Backward-compatible behaviour.
        if (args.Length > 0)
        {
            if (!TryParseCliArgs(args, out var layer, out var opMode, out var serializerMode))
                return;  // invalid args

            BenchmarkLoop.RunBenchmark(layer, opMode, serializerMode);
            return;
        }

        // Interactive mode (no args): loop the menu so the user doesn't have to restart between runs.
        // Q exits the menu (and the application).
        while (true)
        {
            var selection = Menu.ShowInteractiveMenu();
            if (selection == null) return;  // user pressed Q

            BenchmarkLoop.RunBenchmark(selection.Value.layer, BenchmarkOpMode.All, selection.Value.serializerMode);

            System.Console.WriteLine();
            System.Console.WriteLine("─────────────────────────────────────────────────────────────────────");
            System.Console.WriteLine("Returning to menu — press any key to continue, or Q to quit...");
            var key = System.Console.ReadKey(intercept: true);
            if (key.Key == ConsoleKey.Q) return;
            System.Console.WriteLine();
        }
    }

    /// <summary>
    /// Parses CLI arguments into (layer, opMode, serializerMode) and, as a side effect, the active
    /// charset (<see cref="BenchmarkTestDataProvider.LongStringSuffix"/>). Each arg is classified
    /// independently and case-insensitively, so multiple args combine in any order — e.g.
    /// <c>FastestByte AsciiShort</c> or <c>Serialize Large Latin1Short</c>. Per arg, in order:
    /// <c>"quick"</c> (mutates <see cref="Configuration"/> warmup/iter counts), <see cref="SerializerSelectionMode"/>,
    /// <see cref="BenchmarkOpMode"/>, <see cref="BenchmarkLayer"/>, then a charset name
    /// (see <see cref="TryApplyCharsetArg"/>). Unrecognized args are warned and ignored; dimensions left
    /// unset keep their defaults (All, All, Standard, and the <see cref="BenchmarkTestDataProvider.LongStringSuffix"/>
    /// field default for charset). Always returns <c>true</c> (kept for caller-side abort symmetry).
    /// </summary>
    private static bool TryParseCliArgs(string[] args, out BenchmarkLayer layer, out BenchmarkOpMode opMode, out SerializerSelectionMode serializerMode)
    {
        layer = BenchmarkLayer.All;
        opMode = BenchmarkOpMode.All;
        serializerMode = SerializerSelectionMode.Standard;

        // Each arg is classified independently → multiple args combine in any order. Without the
        // charset branch the CLI path never sets the charset, so it silently used the Latin1Long
        // field default — diverging from interactive runs (where the menu pins it).
        foreach (var arg in args)
        {
            // Quick mode: short warmup, few iterations, small sample count. Not an enum value — it's a
            // Configuration meta-flag, so handle it before the enum-parse cascade.
            if (string.Equals(arg, "quick", StringComparison.OrdinalIgnoreCase))
            {
                Configuration.WarmupIterations = 5;
                Configuration.TestIterations = 100;
                Configuration.BenchmarkSamples = 3;
                continue;
            }

            // Serializer-selection (AsyncPipe/FastestByte/Standard).
            if (Enum.TryParse<SerializerSelectionMode>(arg, ignoreCase: true, out var sm))
            {
                serializerMode = sm;
                continue;
            }

            // Op-mode (Serialize/Deserialize/All).
            if (Enum.TryParse<BenchmarkOpMode>(arg, ignoreCase: true, out var om))
            {
                opMode = om;
                continue;
            }

            // Layer (Core/Comprehensive/Edge/Small/Medium/Large/Repeated/Deep/All).
            if (Enum.TryParse<BenchmarkLayer>(arg, ignoreCase: true, out var ly))
            {
                layer = ly;
                continue;
            }

            // Charset (long-string suffix profile) — mirrors the interactive ShowCharsetSettingsMenu.
            if (TryApplyCharsetArg(arg))
                continue;

            // Unknown arg — ignored, defaults stand. Matches prior unrecognized-arg leniency.
            System.Console.Error.WriteLine($"Warning: unrecognized argument '{arg}'. Ignored (defaults: Layer=All, OpMode=All, SerializerMode=Standard, charset unchanged).");
        }

        return true;
    }

    /// <summary>
    /// Maps a case-insensitive charset name to its <see cref="CharsetSuffixes"/> value and assigns
    /// <see cref="BenchmarkTestDataProvider.LongStringSuffix"/>. Names mirror the interactive
    /// <c>ShowCharsetSettingsMenu</c> options. <see cref="CharsetSuffixes"/> members are <c>const string</c>,
    /// so this is a name→value match rather than an <see cref="Enum.TryParse{T}(string, bool, out T)"/>.
    /// Returns <c>false</c> when the name is not a known charset (the caller then treats the arg as unknown).
    /// </summary>
    private static bool TryApplyCharsetArg(string arg)
    {
        string? suffix = arg.ToLowerInvariant() switch
        {
            "asciifix"       => CharsetSuffixes.AsciiFix,
            "asciishort"     => CharsetSuffixes.AsciiShort,
            "asciilong"      => CharsetSuffixes.AsciiLong,
            "latin1fix"      => CharsetSuffixes.Latin1Fix,
            "latin1short"    => CharsetSuffixes.Latin1Short,
            "latin1long"     => CharsetSuffixes.Latin1Long,
            "cjkbmpshort"    => CharsetSuffixes.CjkBmpShort,
            "cjkbmplong"     => CharsetSuffixes.CjkBmpLong,
            "cyrillicshort"  => CharsetSuffixes.CyrillicShort,
            "cyrilliclong"   => CharsetSuffixes.CyrillicLong,
            "mixedshort"     => CharsetSuffixes.MixedShort,
            "mixedlong"      => CharsetSuffixes.MixedLong,
            _ => null
        };
        if (suffix is null)
            return false;
        BenchmarkTestDataProvider.LongStringSuffix = suffix;
        return true;
    }

    // RunBenchmark + RunBenchmarksForTestData + CreateSerializers → BenchmarkLoop.cs

    // Serializer implementations (ISerializerBenchmark + 12 concrete benchmark classes) → Benchmarks/

    // Results / output formatters → Output.cs
    // BenchmarkResult DTO → BenchmarkResult.cs

}
