using MauiTestApp.Command;
using MauiTestApp.Extensions;
using MauiTestApp.Graphing;
using OpenQuantumSafe;

namespace MauiTestApp.ViewModels
{
    public class SIGViewModel : ViewModelBase
    {
        public SIGViewModel()
        {
            TestCommand = new RelayCommand(Test, CanTest);
        }

        private Dictionary<string, string> AlgorithmNames = new()
        {
            { "Dilithium 2", "Dilithium2" },
            { "Dilithium 2 AES", "Dilithium2-AES" },
            { "Dilithium 3", "Dilithium3" },
            { "Dilithium 3 AES", "Dilithium3-AES" },
            { "Dilithium 5", "Dilithium5" },
            { "Dilithium 5 AES", "Dilithium5-AES" },
            { "SPHINCS+ SHA (1/s/s)", "SPHINCS+-SHA256-128s-simple" },
            { "SPHINCS+ SHA (1/f/s)", "SPHINCS+-SHA256-128f-simple" },
            { "SPHINCS+ SHA (1/s/r)", "SPHINCS+-SHA256-128s-robust" },
            { "SPHINCS+ SHA (1/f/r)", "SPHINCS+-SHA256-128f-robust" },
            { "SPHINCS+ SHA (3/s/s)", "SPHINCS+-SHA256-192s-simple" },
            { "SPHINCS+ SHA (3/f/s)", "SPHINCS+-SHA256-192f-simple" },
            { "SPHINCS+ SHA (5/s/s)", "SPHINCS+-SHA256-256s-simple" },
            { "SPHINCS+ SHA (5/f/s)", "SPHINCS+-SHA256-256f-simple" },
            { "SPHINCS+ Haraka (1/s/s)", "SPHINCS+-Haraka-128s-simple" },
            { "SPHINCS+ Haraka (1/f/s)", "SPHINCS+-Haraka-128f-simple" },
            { "SPHINCS+ Haraka (1/s/r)", "SPHINCS+-Haraka-128s-robust" },
            { "SPHINCS+ Haraka (1/f/r)", "SPHINCS+-Haraka-128f-robust" },
            { "SPHINCS+ Haraka (3/s/s)", "SPHINCS+-Haraka-192s-simple" },
            { "SPHINCS+ Haraka (3/f/s)", "SPHINCS+-Haraka-192f-simple" },
            { "SPHINCS+ Haraka (5/s/s)", "SPHINCS+-Haraka-256s-simple" },
            { "SPHINCS+ Haraka (5/f/s)", "SPHINCS+-Haraka-256f-simple" },
            { "SPHINCS+ SHAKE (1/s/s)", "SPHINCS+-SHAKE256-128s-simple" },
            { "SPHINCS+ SHAKE (1/f/s)", "SPHINCS+-SHAKE256-128f-simple" },
            { "SPHINCS+ SHAKE (1/s/r)", "SPHINCS+-SHAKE256-128s-robust" },
            { "SPHINCS+ SHAKE (1/f/r)", "SPHINCS+-SHAKE256-128f-robust" },
            { "SPHINCS+ SHAKE (3/s/s)", "SPHINCS+-SHAKE256-192s-simple" },
            { "SPHINCS+ SHAKE (3/f/s)", "SPHINCS+-SHAKE256-192f-simple" },
            { "SPHINCS+ SHAKE (5/s/s)", "SPHINCS+-SHAKE256-256s-simple" },
            { "SPHINCS+ SHAKE (5/f/s)", "SPHINCS+-SHAKE256-256f-simple" }

        };

        public string[] Algorithms => AlgorithmNames.Keys.ToArray();

        private string _SelectedAlgorithm { get; set; } = "";
        public string SelectedAlgorithm
        {
            get => _SelectedAlgorithm;
            set
            {
                _SelectedAlgorithm = value;
                OnPropertyChanged(nameof(SelectedAlgorithm));
                TestCommand.RaiseCanExecuteChanged();
            }
        }

        public event EventHandler? OnDataUpdate;

        public RelayCommand TestCommand { get; private set; }

        private bool CanTest() => AlgorithmNames.ContainsKey(SelectedAlgorithm);

        public RunData KeypairData { get; set; } = new();
        public RunData SignData { get; set; } = new();
        public RunData VerifyData { get; set; } = new();

        public List<float> KeypairRuns { get; set; } = new();
        public List<float> SignRuns { get; set; } = new();
        public List<float> VerifyRuns { get; set; } = new();

        public string SignatureLength { get; set; } = "";
        public string SecretKeyLength { get; set; } = "";
        public string PublicKeyLength { get; set; } = "";

        private const int Runs = 20;
        private double GetCPUUsage(TimeSpan cpuDiff, TimeSpan diff)
        {
            return 100.0 / Environment.ProcessorCount * cpuDiff.Ticks / diff.Ticks;
        }

        private double GetTimeAvg(TimeSpan diff) => diff.TotalMilliseconds / Runs;

        private double GetMemoryAvg(long prev) => (GC.GetTotalAllocatedBytes(true) - (double)prev) / Runs;

        private void Test()
        {
            KeypairRuns.Clear();
            SignRuns.Clear();

            var sig = new Sig(AlgorithmNames[SelectedAlgorithm]);
            SignatureLength = $"{sig.SignatureLength} B";
            SecretKeyLength = $"{sig.SecretKeyLength} B";
            PublicKeyLength = $"{sig.PublicKeyLength} B";

            var proc = System.Diagnostics.Process.GetCurrentProcess();

            TestKeypair(sig, proc);
            TestSign(sig, proc);

            UpdateAll();
        }

        private void TestKeypair(Sig sig, System.Diagnostics.Process proc)
        {
            long prev = GC.GetTotalAllocatedBytes(true);

            var startCPU = proc.TotalProcessorTime;
            var start = DateTime.Now;

            for (int i = 0; i < Runs; i++)
            {
                var avgStart = DateTime.Now;
                sig.keypair(out var publicKeyA, out var privateKeyA);
                KeypairRuns.Add((float)(DateTime.Now - avgStart).TotalMilliseconds);
            }

            var endCPU = proc.TotalProcessorTime;
            var end = DateTime.Now;

            KeypairData.SetMemory(GetMemoryAvg(prev));
            KeypairData.SetCPU(GetCPUUsage(endCPU - startCPU, end - start));
            KeypairData.SetTime(GetTimeAvg(end - start));
        }

        private void TestSign(Sig sig, System.Diagnostics.Process proc)
        {
            long prev = GC.GetTotalAllocatedBytes(true);

            sig.keypair(out var publicKeyA, out var privateKeyA);

            var startCPU = proc.TotalProcessorTime;
            var start = DateTime.Now;

            for (int i = 0; i < Runs; i++)
            {
                byte[] message = "PQC na mobilnim zarizeni".AsBytes();
                var avgStart = DateTime.Now;
                sig.sign(out var ciphertextB, message, publicKeyA);
                SignRuns.Add((float)(DateTime.Now - avgStart).TotalMilliseconds);
            }

            var endCPU = proc.TotalProcessorTime;
            var end = DateTime.Now;

            SignData.SetMemory(GetMemoryAvg(prev));
            SignData.SetCPU(GetCPUUsage(endCPU - startCPU, end - start));
            SignData.SetTime(GetTimeAvg(end - start));
        }

        private void UpdateAll()
        {
            OnPropertyChanged(nameof(KeypairData));
            OnPropertyChanged(nameof(SignData));

            OnPropertyChanged(nameof(PublicKeyLength));
            OnPropertyChanged(nameof(SecretKeyLength));
            OnPropertyChanged(nameof(SignatureLength));

            OnDataUpdate?.Invoke(null, new());
        }
    }
}
