using System.Collections.Immutable;
using System.Net;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.Json.Serialization;
using Ethanol.ContextBuilder.Serialization;
using Ethanol.DataObjects; 
using Microsoft.Extensions.Logging;
/// <summary>
/// Represents a class that contains commands for scanning and detecting malware indicators.
/// </summary>
/// <remarks>
/// This class provides functionality to analyze input data against a malware profile and generate a report indicating the presence of malware.
/// </remarks>
[Command("malware", "Malware detection commands.")]
internal class MalwareSonarCommands : ConsoleAppBase
{
    private readonly ILogger? _logger;

    /// <summary>
    /// Initializes a new instance of the <see cref="MalwareSonarCommands"/> class.
    /// </summary>
    /// <param name="logger">The logger to be used for logging.</param>
    public MalwareSonarCommands(ILogger<ContextProviderCommand>? logger)
    {
        _logger = logger;
    }
    /// <summary>
    /// Tests the specified context data for the presence of malware indicators.
    /// This method analyzes the given input against a malware profile and generates a report.
    /// </summary>
    /// <param name="pathMalwareProfileFile">
    /// The path to the malware profile file. This file contains the definitions or patterns used to identify malware.
    /// </param>
    /// <param name="inputPath">
    /// The path to the input folder or input file containing context data to be tested for malware. This can be a directory path or a path to a specific file.
    /// </param>
    /// <param name="outputFilePath">
    /// The path where the output JSON report will be saved. This report contains the results of the malware test.
    /// </param>
    /// <param name="thresholdScore">
    /// The threshold score for determining the presence of malware. Defaults to 1.0. This score is used to gauge the likelihood of malware presence based on the analysis.
    /// </param>
    /// <returns>
    /// The method does not return a value, but it generates a report in JSON format at the specified output file path.
    /// </returns>
    /// <remarks>
    /// Ensure that the paths provided for the malware profile file, input data, and output report are accessible and valid.
    /// The format and structure of the malware profile file should be consistent with the expected patterns or definitions used for malware detection.
    /// </remarks>
    /// <example>
    /// <code>
    /// ScanMalware("path/to/malwareProfile.mal", "path/to/inputData", "path/to/outputReport.json", 0.5);
    /// </code>
    /// This example demonstrates calling the ScanMalware method with specified paths and a custom threshold score.
    /// </example>

    [Command("scan", "Tests the specified context for the presence of malware indicators.")]
    public void ScanMalware(
        [Option("p", "The path to the malware profile file. This file contains the definitions or patterns used to identify malware.")] string pathMalwareProfileFile,
        [Option("i", "The path to the input folder or input file containing context data to be tested for malware. This can be a directory path or a path to a specific file.")] string inputPath,
        [Option("o", "The path where the output JSON report will be saved. This report contains the results of the malware test.")] string outputFilePath,
        [Option("t", "The threshold score for determining the presence of malware. Defaults to 1.0. This score is used to gauge the likelihood of malware presence based on the analysis.")] double thresholdScore = 1.0
    )
    {
        var fullMalwareProfilePath = Path.GetFullPath(pathMalwareProfileFile);
        var fullInputPath = Path.GetFullPath(inputPath);
        var fullOutputPathRoot = Path.GetPathRoot(Path.GetFullPath(outputFilePath));
        Path.Exists(fullMalwareProfilePath).ElseThrow(() => new ArgumentException($"The profiles file '{fullMalwareProfilePath}' does not exist."));
        Path.Exists(fullInputPath).ElseThrow(() => new ArgumentException($"The input path '{fullInputPath}' does not exist."));
        Path.Exists(fullOutputPathRoot).ElseThrow(() => new ArgumentException($"The output path '{fullOutputPathRoot}' does not exist."));

        _logger?.LogInformation($"Loading malware profile from: '{fullMalwareProfilePath}'");
        var malwareProfile = MalwareDetectionProfile.LoadFrom(fullMalwareProfilePath);
        if (malwareProfile == null)
        {
            _logger?.LogError($"Failed to load malware profile from: '{fullMalwareProfilePath}'");
            return;
        }

        _logger?.LogInformation($"Loading context data from: '{fullInputPath}'");
        var inputContextFiles = Directory.Exists(fullInputPath) ? Directory.GetFiles(fullInputPath) : new string[] { fullInputPath };

        foreach (var contextFile in inputContextFiles)
        {
            _logger?.LogInformation($"  Context file '{contextFile}' open, checking malware indicators.");

            using var contextReader = File.OpenText(contextFile);            
            using var reportWriter = File.CreateText(Path.Combine(fullOutputPathRoot!, Path.GetFileNameWithoutExtension(contextFile) + ".out.json"));

            var malwareChecker = new MalwareChecker(malwareProfile, thresholdScore, reportWriter, MalwareReportFormat.Json, _logger);
            var contextData = ReadFromFile(contextReader);
            foreach (var data in contextData)
            {
                malwareChecker.CheckMalware(data);
            }
        }
    }

    enum MalwareReportFormat { Markdown, Csv, Json, Null }

    /// <summary>
    /// Tests the specified context for the presence of malware indicators. It reads context data from stdin and writes the report to stdout.
    /// </summary>
    /// <param name="pathMalwareProfileFile">The path to the malware profile file. This file contains the definitions or patterns used to identify malware.</param>
    /// <param name="thresholdScore">The threshold score for determining the presence of malware. Defaults to 1.0. This score is used to gauge the likelihood of malware presence based on the analysis.</param>
    [Command("pscan", "Tests the specified context for the presence of malware indicators.")]
    public void PipelineScanMalware(
        [Option("p", "The path to the malware profile file. This file contains the definitions or patterns used to identify malware.")] string pathMalwareProfileFile,
        [Option("t", "The threshold score for determining the presence of malware. Defaults to 1.0. This score is used to gauge the likelihood of malware presence based on the analysis.")] double thresholdScore = 1.0,
        [Option("f", "Output report format. Defaults to json. Other possibilities are 'csv', 'markdown'")] string format = "json"
    )
    {
        var fullMalwareProfilePath = Path.GetFullPath(pathMalwareProfileFile); 
        Path.Exists(fullMalwareProfilePath).ElseThrow(() => new ArgumentException($"The profiles file '{fullMalwareProfilePath}' does not exist."));
        
        var reportFormat = Enum.TryParse<MalwareReportFormat>(format, true, out var f) ? f : throw new ArgumentException($"Invalid output format specified '{format}'.");

        _logger?.LogInformation($"Loading malware profile from: '{fullMalwareProfilePath}'");
        var malwareProfile = MalwareDetectionProfile.LoadFrom(fullMalwareProfilePath);
        if (malwareProfile == null)
        {
            _logger?.LogError($"Failed to load malware profile from: '{fullMalwareProfilePath}'");
            return;
        }
        var contextReader = Console.In;
        var reportWriter = Console.Out;

        var malwareChecker = new MalwareChecker(malwareProfile, thresholdScore, reportWriter, reportFormat, _logger);
        var contextData = ReadFromFile(contextReader);
        foreach (var data in contextData)
        {
            malwareChecker.CheckMalware(data);
        }
    }

        /// <summary>
    /// Tests the specified context for the presence of malware indicators. It reads context data from stdin and writes the report to stdout.
    /// </summary>
    /// <param name="pathMalwareProfileFile">The path to the malware profile file. This file contains the definitions or patterns used to identify malware.</param>
    /// <param name="thresholdScore">The threshold score for determining the presence of malware. Defaults to 1.0. This score is used to gauge the likelihood of malware presence based on the analysis.</param>
    [Command("eval", "Tests the specified context for the presence of malware indicators.")]
    public void PipelineEvalMalware(
        [Option("p", "The path to the malware profile file. This file contains the definitions or patterns used to identify malware.")] string pathMalwareProfileFile,
        [Option("m", "Meta information on the source of flows.")] string metadataFile
    )
    {
        var fullMalwareProfilePath = Path.GetFullPath(pathMalwareProfileFile); 
        Path.Exists(fullMalwareProfilePath).ElseThrow(() => new ArgumentException($"The profiles file '{fullMalwareProfilePath}' does not exist."));

        var fullMetadataPath = Path.GetFullPath(metadataFile);
        Path.Exists(fullMetadataPath).ElseThrow(() => new ArgumentException($"The metadata file '{fullMetadataPath}' does not exist."));
        
         _logger?.LogInformation($"Loading metadata from: '{fullMetadataPath}'");
        var metadata = InfectedMetadata.LoadFromFile(fullMetadataPath);
            
        _logger?.LogInformation($"Metadata: {metadata}");

        _logger?.LogInformation($"Loading malware profile from: '{fullMalwareProfilePath}'");
        var malwareProfile = MalwareDetectionProfile.LoadFrom(fullMalwareProfilePath);
        if (malwareProfile == null)
        {
            _logger?.LogError($"Failed to load malware profile from: '{fullMalwareProfilePath}'");
            return;
        }
        var contextReader = Console.In;
        var reportWriter = Console.Out;

        var malwareChecker = new MalwareChecker(malwareProfile, 1, reportWriter, MalwareReportFormat.Null , _logger);
        var contextData = ReadFromFile(contextReader).ToList();
       

        

        var firstContextStart = contextData.First().Start;
        var timeShift = firstContextStart - metadata.CaptureStart;
        var contextTimeInfectedStart = metadata.InfectionTime + timeShift;
        var contextTimeInfectedEnd = contextTimeInfectedStart + metadata.InfectionDuration * 2;
        _logger?.LogInformation($"Dataset start: {firstContextStart}, infection time={contextTimeInfectedStart}");

        var tresholdValues = new[] { 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 };

        reportWriter.WriteLine($"start,host,label,{String.Join(',', tresholdValues.Select(t => $"T_{t}"))}");

        foreach (var data in contextData)
        {
            var groundTruth = (data.Host == metadata.InfectedHost && TimeOverlaps(data.Start, data.End, contextTimeInfectedStart, contextTimeInfectedEnd)) ? metadata.MalwareFamily : "normal";

            var rowDetection = new List<string>();
            var report = malwareChecker.CheckMalware(data);
            var detected = report.Scores.FirstOrDefault();
            foreach (var treshold in tresholdValues)
            {
                if (detected != null && detected?.Score.Average() >= treshold)
                {
                    var detectedMalware = malwareProfile.GetModel(detected.Family);
                    rowDetection.Add(detectedMalware?.Name ?? "unknown");
                }
                else
                {
                    rowDetection.Add("normal");
                }
            }
            reportWriter.WriteLine($"{data.Start.ToString("yyyy-MM-dd HH:mm:ss")}, {data.Host}, {groundTruth}, {String.Join(',', rowDetection)}");
        }
    }

    private bool TimeOverlaps(DateTimeOffset firstStart, DateTimeOffset firstEnd, DateTimeOffset secondStart, DateTimeOffset secondEnd)
    {
        return firstStart <= secondEnd && firstEnd >= secondStart;
    }
    /// <summary>
    /// Represents a class that checks for malware using a specified malware detection profile and threshold score.
    /// </summary>
    class MalwareChecker
    {
        readonly private MalwareDetectionProfile malwareProfile;
        readonly private double thresholdScore;
        readonly private ILogger? _logger;
        readonly private TextWriter? _reportWriter;
        private readonly MalwareReportFormat _format;

        public MalwareChecker(MalwareDetectionProfile malwareProfile, double thresholdScore, TextWriter? reportWriter, MalwareReportFormat format, ILogger? logger)
        {
            this.malwareProfile = malwareProfile;
            this.thresholdScore = thresholdScore;
            this._logger = logger;
            this._reportWriter = reportWriter;
            this._format = format;
        }

        public MalwareDetectionReport CheckMalware(ContextIndicators data)
        {
            _logger?.LogInformation($"    Checking context: host={data.Host}, ips={data.Ips.Length}, domains={data.Domains.Length}, urls={data.Urls.Length}.");
            var matches = malwareProfile.Models.Select(model => model.Match(data));

            var scoreReports = matches.Select(m => MalwareScoreReport.Create(m.Malware,
            new[]{
                    CompressScore(m.ScoreIps.Score , m.ScoreIps.Threshold.Mean + m.ScoreIps.Threshold.Stdev),
                    CompressScore(m.ScoreIps.Score , m.ScoreIps.Threshold.Mean),
                    CompressScore(m.ScoreIps.Score , m.ScoreIps.Threshold.Mean - m.ScoreIps.Threshold.Stdev)},
            new[]{
                    CompressScore(m.ScoreDomains.Score , m.ScoreDomains.Threshold.Mean + m.ScoreDomains.Threshold.Stdev),
                    CompressScore(m.ScoreDomains.Score , m.ScoreDomains.Threshold.Mean),
                    CompressScore(m.ScoreDomains.Score , m.ScoreDomains.Threshold.Mean - m.ScoreDomains.Threshold.Stdev)},
            new[]{
                    CompressScore(m.ScoreUrls.Score , m.ScoreUrls.Threshold.Mean + m.ScoreUrls.Threshold.Stdev),
                    CompressScore(m.ScoreUrls.Score , m.ScoreUrls.Threshold.Mean),
                    CompressScore(m.ScoreUrls.Score , m.ScoreUrls.Threshold.Mean - m.ScoreUrls.Threshold.Stdev)}
            )).OrderByDescending(x => x.Score.Average()).ToArray();

            var malwareDetectionReport = new MalwareDetectionReport(data.Start, data.End, data.Host, scoreReports, matches.ToArray());

            switch (_format)
            {
                case MalwareReportFormat.Csv:
                    WriteCsvReport(malwareDetectionReport);
                    break;
                case MalwareReportFormat.Markdown:
                    WriteMarkdownReport(malwareDetectionReport);
                    break;
                case MalwareReportFormat.Json:
                    WriteJsonReport(malwareDetectionReport);
                    break;
            }
            return malwareDetectionReport;
        }

        private void WriteCsvReport(MalwareDetectionReport malwareDetectionReport)
        {
            throw new NotImplementedException();
        }

        private void WriteMarkdownReport(MalwareDetectionReport malwareDetectionReport)
        {
            _reportWriter?.WriteLine($"## Context {malwareDetectionReport.Host} [{malwareDetectionReport.WindowStart}-{malwareDetectionReport.WindowEnd}]");
            _reportWriter?.WriteLine();

            var detected = malwareDetectionReport.Scores.FirstOrDefault();
            if (detected != null && detected?.Score.Average() >= thresholdScore)
            {
                var detectedMalware = malwareProfile.GetModel(detected.Family);

                _reportWriter?.WriteLine($"  + Suspicious communication detected: family={detected.Family}, score={detected.Score.Average():F2}");
                _reportWriter?.WriteLine($"  + {detectedMalware?.Name} information: {detectedMalware?.Description} See more at '{detectedMalware?.InformationRefUrl}'");
            }
            else
            {
                _reportWriter?.WriteLine();
                _reportWriter?.WriteLine($"  - No malware detected in the context.");
            }
            _reportWriter?.WriteLine();
            _reportWriter?.WriteLine($"    | family               |            ips |        domains |           urls |  score |");
            _reportWriter?.WriteLine("    |----------------------|----------------|----------------|----------------|--------|");
            foreach (var m in malwareDetectionReport.Scores)
            {
                _reportWriter?.WriteLine($"    | {m.Family,-20} |{String.Join(',', m.IpsScore.Select(s => s.ToString("F2"))),16}|{String.Join(',', m.DomainsScore.Select(s => s.ToString("F2"))),16}|{String.Join(',', m.UrlsScore.Select(s => s.ToString("F2"))),16}|{m.Score.Average().ToString("F2"),8}|");
            }
            _reportWriter?.WriteLine();
        }

        private void WriteJsonReport(MalwareDetectionReport malwareDetectionReport)
        {
            var json = JsonSerializer.Serialize(malwareDetectionReport, new JsonSerializerOptions { NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals });
            _reportWriter?.WriteLine(json);
        }
        
        /// <summary>
        /// Compresses a given score into a logarithmic scale based on a specified threshold.
        /// This method transforms the raw score using a logarithmic function, making it more suitable for analysis or comparison when dealing with wide-ranging values.
        /// </summary>
        /// <param name="score">
        /// The original score to be compressed. This score represents a raw value that can range widely and needs to be normalized or scaled down.
        /// </param>
        /// <param name="threshold">
        /// The threshold value used for scaling the score. This value acts as a normalization factor in the compression formula and should be non-zero to avoid division by zero errors.
        /// </param>
        /// <returns>
        /// The compressed score calculated using a logarithmic scale. This transformation adjusts the score into a more manageable range, facilitating easier analysis or comparison.
        /// </returns>
        /// <remarks>
        /// The compression is achieved by scaling the score relative to the threshold and then applying the logarithmic function. The method is especially useful in scenarios where the scores exhibit a large dynamic range and need to be normalized for better interpretability or comparison.
        /// </remarks>
        /// <exception cref="System.DivideByZeroException">
        /// Thrown if the threshold is zero, leading to a division by zero during the calculation.
        /// </exception>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// Thrown if the calculated value is outside the range of possible values for a double.
        /// </exception>
        double CompressScore(double score, double threshold)
        {
            return Math.Log10(Math.Abs(score * 10 / threshold + 1));
        }
    }

    /// <summary>
    /// Represents a report detailing the scores associated with different aspects of malware detection.
    /// </summary>
    /// <param name="Family">The family name of the malware.</param>
    /// <param name="IpsScore">An array of scores related to IP addresses.</param>
    /// <param name="DomainsScore">An array of scores related to domain names.</param>
    /// <param name="UrlsScore">An array of scores related to URLs.</param>
    /// <param name="Score">An array of scores representing the average score across the different categories.</param>
    /// <remarks>
    /// The scores in the arrays represent the likelihood or intensity of the malware's presence in the corresponding categories.
    /// </remarks>
    public record MalwareScoreReport(string Family, double[] IpsScore, double[] DomainsScore, double[] UrlsScore, double[] Score)
    {
        /// <summary>
        /// Calculates the average score for a specific index across the different categories (IPs, Domains, URLs).
        /// </summary>
        /// <param name="index">The index for which to calculate the score.</param>
        /// <returns>
        /// The average score at the specified index. If the index is out of range for any array, or if the score is NaN, it is not included in the calculation.
        /// </returns>
        static double[] GetScore(double[] ipsScore, double[] domainsScore, double[] urlsScore)
        {
            if (ipsScore.Length != domainsScore.Length || ipsScore.Length != urlsScore.Length)
            {
                throw new ArgumentException("The length of the arrays must be the same.");
            }
            var result = new double[ipsScore.Length];
            for (int index = 0; index < ipsScore.Length; index++)
            {            
                var count = 0;
                var sum = 0.0;
                if (!double.IsNaN(ipsScore[index])) { sum += ipsScore[index]; count++; }
                if (!double.IsNaN(domainsScore[index])) { sum += domainsScore[index]; count++; }
                if (!double.IsNaN(urlsScore[index])) { sum += urlsScore[index]; count++; }
                result[index] = count > 0 ? sum / count : 0.0;
            }
            return result;
        }
        /// <summary>
        /// Creates a new instance of the <see cref="MalwareScoreReport"/> record with specified scores for different malware indicators.
        /// </summary>
        /// <param name="Family">The family name of the malware. This typically refers to a broad classification of the malware based on its behavior, characteristics, or origin.</param>
        /// <param name="IpsScore">An array of scores related to IP addresses. These scores represent the likelihood or intensity of the malware's presence in relation to various IP addresses.</param>
        /// <param name="DomainsScore">An array of scores related to domain names. These scores indicate the potential association of these domains with the malware.</param>
        /// <param name="UrlsScore">An array of scores related to URLs. These scores assess the relevance or involvement of these URLs with the malware.</param>
        /// <returns>
        /// A new instance of the <see cref="MalwareScoreReport"/> record populated with the provided malware family name and scores for IPs, domains, and URLs.
        /// </returns>
        /// <remarks>
        /// This method is a factory for creating <see cref="MalwareScoreReport"/> instances, ensuring that they are constructed with a consistent set of data.
        /// </remarks>
        public static MalwareScoreReport Create(string Family, double[] IpsScore, double[] DomainsScore, double[] UrlsScore)
        {
            return new MalwareScoreReport(Family, IpsScore, DomainsScore, UrlsScore, GetScore(IpsScore, DomainsScore, UrlsScore));      
        }
    }
    /// <summary>
    /// Represents a comprehensive report of malware detection over a specified time window.
    /// </summary>
    /// <param name="WindowStart">The start date and time of the window for malware detection.</param>
    /// <param name="WindowEnd">The end date and time of the window for malware detection.</param>
    /// <param name="Host">The host for which the report is generated.</param>
    /// <param name="Scores">An array of <see cref="MalwareScoreReport"/> instances, each representing the scores for different malware families.</param>
    /// <param name="Matches">An array of <see cref="MalwareMatch"/> instances, representing individual matches of malware found.</param>
    /// <remarks>
    /// This report provides a time-bound overview of malware detection, detailing both aggregated scores and specific matches found.
    /// </remarks>
    public record MalwareDetectionReport(DateTimeOffset WindowStart, DateTimeOffset WindowEnd, string Host, MalwareScoreReport[] Scores, MalwareMatch[] Matches);

    /// <summary>
    /// Retrieves the context indicators of an activity.
    /// </summary>
    /// <param name="context">The host context.</param>
    /// <returns>The context indicators.</returns>
    ContextIndicators GetContextIndicatorsOfActivity(HostContext context)
    {
        return new ContextIndicators(context.Start, context.End, context.Key ?? IPAddress.None.ToString(),
        context.WebUrls.AsSafe().Select(x => x.Url).ToArray(),
        context.ResolvedDomains.AsSafe().Select(d => d.QueryString).ToArray() ,
        context.Connections.AsSafe().Select(c => c.RemoteHostAddress).ToArray()
        );
    }

    IEnumerable<ContextIndicators> ReadFromFile(TextReader reader)
    {
        string? line;
        while ((line = reader.ReadLine()) != null) 
        {
            var context = Json.Deserialize<HostContext>(line);
            if (context!=null)
                yield return GetContextIndicatorsOfActivity(context);
        }
    }

    /// <summary>
    /// Learns malware profile consisting of models from specified malware reports.
    /// This method processes malware reports located in a specified root folder and compiles them into comprehensive malware profiles. These profiles are then saved to a designated output folder. This process is crucial for aggregating and analyzing data from multiple reports to create a consolidated view of malware characteristics and behaviors.
    /// </summary>
    /// <param name="rootReportFolder">
    /// Path to the root folder containing the malware reports. 
    /// This folder should contain files or subfolders with data that can be processed into malware profiles. 
    /// The method expects this path to lead to a valid directory where the reports are stored in a format that can be read and compiled.
    /// </param>
    /// <param name="outputProfilePath">
    /// Path to the output folder where the malware profiles will be saved. 
    /// This is the destination directory where the resulting malware profiles are stored after processing. 
    /// Ensure this path points to a valid directory with appropriate write permissions.
    /// </param>
    /// <remarks>
    /// The method systematically goes through the malware reports in the rootReportFolder, extracts necessary data, and compiles it into structured profiles. The resulting profiles are then stored in the outputProfilePath. This is an essential step in preparing data for further malware analysis and threat intelligence activities.
    /// </remarks>
    [Command("learn", "Learn malware models from specified malware reports.")]
    public void LearnMalwareProfile(
        [Option("r", "Path to the root folder containing the malware reports.")] string rootReportFolder,
        [Option("o", "Path to the output folder where the malware profiles will be saved.")] string outputProfilePath
    )
    {
        var rootReportFolderPath = Path.GetFullPath(rootReportFolder);
        var outputFolderPath = Path.GetFullPath(outputProfilePath);

        Path.Exists(Path.GetDirectoryName(outputFolderPath)).ElseThrow(() => new ArgumentException($"The output folder '{outputFolderPath}' does not exist."));

        _logger?.LogInformation($"Building malware profiles from reports in folder: '{rootReportFolderPath}'");

        var families = Directory.GetDirectories(rootReportFolderPath);
        var models = new List<MalwareModel>();
        foreach (var family in families)
        {
            try
            {
                var familyName = Path.GetFileName(family);
                _logger?.LogInformation($"Reading reports for family '{familyName}'.");

                var samples = Directory.GetFiles(family);
                var reports = samples.Select(MalwareReportLoader.LoadReport).Where(x => x != null).Select(x => x!).ToList();
                var indicators = MalwareReportLoader.GetMalwareIndicators(familyName, reports);

                var firstReport = reports.FirstOrDefault();
                var signature = firstReport?.Signatures?.Where(s => s.Tags?.Any(t => t.StartsWith($"family:{familyName}")) ?? false).FirstOrDefault();
                var description = signature?.Desc ?? string.Empty;
                var malwareName = signature?.Name ?? familyName;
                var builder = new MalwareModelBuilder(familyName, malwareName) { Description = description ?? string.Empty };

                builder.AddMalwareIndicators(indicators);
                var model = builder.Build();
                if (model != null)
                {
                    models.Add(model);
                    _logger?.LogInformation($"Add model for family '{familyName}'.");
                }
            }
            catch (Exception ex)
            {
                _logger?.LogError(ex, $"Failed to build malware profile for '{family}'.");
            }
        }
        var malwareProfile = new MalwareDetectionProfile(models.ToArray());
        malwareProfile.SaveTo(outputFolderPath);
        _logger?.LogInformation($"Profile save to '{outputFolderPath}'.");
    }


    /// <summary>
    /// Modifies the input context by 'infecting' it with the malware from malware reports in the given folder.
    /// </summary>
    /// <param name="inputContextFilePath">Path to the input context file.</param>
    /// <param name="rootReportFolder">Path to the root folder containing the malware reports.</param>
    /// <param name="outputContextFilePath">Path to the output folder where the malware profiles will be saved.</param>
    [Command("infect", "Modify the input context by 'infecting' it with the malware from malware reports in the given folder.")]
    public void InfectContextWithMalware(
        [Option("i", "Path to the input context file.")] string inputContextFilePath,
        [Option("r", "Path to the root folder containing the malware reports.")] string rootReportFolder,
        [Option("o", "Path to the output folder where the malware profiles will be saved.")] string outputContextFilePath
    )
    {
        if (!Path.Exists(inputContextFilePath)) throw new ArgumentException($"The input context file '{inputContextFilePath}' does not exist.");

        var contexts = File.ReadAllLines(inputContextFilePath).Select(line => Json.Deserialize<HostContext>(line)).Where(c=>c!=null).Select(c=>c!).ToArray();
        var testIocs = new List<MalwareIndicators>();

        foreach (var family in Directory.GetDirectories(rootReportFolder))
        {
            var familyName = Path.GetFileName(family);
            var samples = Directory.GetFiles(family);

            foreach (var sample in samples)
            {
                var report = JsonSerializer.Deserialize<Ethanol.MalwareSonar.MalwareReport.Report>(File.ReadAllText(sample));
                if (report != null)
                {
                    var sampleIocs = report.Targets?.Select(t => MalwareReportLoader.NormalizeIocs(familyName, report.Sample?.Id, t.Iocs)).Where(x=>x!=null) ?? Enumerable.Empty<MalwareIndicators>();
                    testIocs.AddRange(sampleIocs!);
                }
            }
        }
        var infectedContexts = contexts.SelectMany(c => testIocs.Select(t =>
        {
            return new HostContext
            {
                Id = c.Id,
                Key = $"{c.Key}-{t.Family}:{t.SampleId}",
                Start = c.Start,
                End = c.End,
                ResolvedDomains = c.ResolvedDomains.AsSafe().Concat(t.Domains.Select(d => new ResolvedDomainInfo("8.8.8.8", d, d, DnsResponseCode.NoError))).ToArray(),
                Connections = c.Connections.AsSafe().Concat(t.Ips.Select(i => new IpConnectionInfo(i, String.Empty, 0, String.Empty, Array.Empty<InternetServiceTag>(), 0,0,0,0,0))).ToArray(),
                WebUrls = c.WebUrls.AsSafe().Concat(t.Urls.Select(u => new WebRequestInfo("0.0.0.0", String.Empty, 80, String.Empty, Array.Empty<InternetServiceTag>(), "GET", u))).ToArray(),
                TlsHandshakes = c.TlsHandshakes.AsSafe()
            };

        }));
        File.WriteAllLines(outputContextFilePath, infectedContexts.Select(c => Json.Serialize(c)));
    }
}

internal record InfectedMetadata
{
    public string InfectedCatpure { get; private set; } = String.Empty;
    public string BackgroundCapture { get; private set; } = String.Empty;
    public string MalwareCapture { get; private set; } = String.Empty;
    public string MalwareHost { get; private set; } = String.Empty;
    public string InfectedHost { get; private set; } = String.Empty;
    public DateTimeOffset CaptureStart { get; private set; }
    public DateTimeOffset CaptureEnd { get; private set; }
    public DateTimeOffset InfectionTime { get; private set; }
    public TimeSpan InfectionDuration { get; private set; }

    public string MalwareFamily => InfectedCatpure.Split('-')?.LastOrDefault()?.Split('.').First() ?? "malware";

    internal static InfectedMetadata LoadFromFile(string fullMalwareProfilePath)
    {
        var value = new InfectedMetadata();
        var lines = File.ReadAllLines(fullMalwareProfilePath);
        foreach(var line in lines)
        {
            var keyVal = line.Split(':', 2, StringSplitOptions.TrimEntries);
            switch(keyVal[0])
            {
                case "Infected Capture": value.InfectedCatpure = keyVal[1]; break;
                case "Background Capture": value.BackgroundCapture = keyVal[1]; break;
                case "Malware Capture": value.MalwareCapture = keyVal[1]; break;
                case "Malware Host": value.MalwareHost = keyVal[1]; break;
                case "Infected Host": value.InfectedHost = keyVal[1]; break;
                case "Capture Start": value.CaptureStart = DateTime.Parse(keyVal[1]); break;
                case "Capture End": value.CaptureEnd = DateTime.Parse(keyVal[1]); break;
                case "Infection Time": value.InfectionTime = DateTime.Parse(keyVal[1]); break;
                case "Infection Duration": value.InfectionDuration = TimeSpan.Parse(keyVal[1]); break;
            }
        }
        return value;
    }
}