using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using ms_useCaseService.Database;
using ms_useCaseService.Database.Models;
using webApiGateway.Objects.Objects;

namespace ms_useCaseService.Mapping;

/// <summary>
/// Provides methods for mapping use cases based on correlation rules stored in a database.
/// </summary>
public class MainMapping
{
    /// <summary>
    /// Represents the logger instance for this class.
    /// </summary>
    private static ILogger Logger = Logging.LoggerFactory.CreateLogger<MainMapping>();
    
    /// <summary>
    /// Configuration to get the database connection string.
    /// </summary>
    private static IConfigurationRoot Configuration = new ConfigurationBuilder()
        .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
        .AddJsonFile("appsettings.json", true, true)
        .Build();
    
    /// <summary>
    /// Setting the database connection.
    /// </summary>
    private static DbContextOptionsBuilder<RulesContext> OptionsBuilder =
        new DbContextOptionsBuilder<RulesContext>().UseNpgsql(Configuration.GetConnectionString("Database"));

    /// <summary>
    /// DbContext class instance for the database communication
    /// </summary>
    private static RulesContext Context = new(OptionsBuilder.Options);

    /// <summary>
    /// Gets a list of use cases mapped from the correlation rules stored in the database for the specified log source.
    /// </summary>
    /// <param name="logsource">The log source for which to get the mapped use cases.</param>
    /// <returns>A list of mapped use cases.</returns>
    public async static Task<List<UseCase>> GetMappedUseCases(string logsource)
    {
        // The list of mapped use cases
        List<UseCase> outputUseCases = new List<UseCase>();

        // Get the rule types for the specified log source
        // RuleType in database is actually a Title in UC object
        List<RuleType> RuleTypes = GetTitles(logsource);

        if (RuleTypes.Count == 0)
        {
            // If no correlation rules were found for the specified log source, create a use case with a message indicating this and add it to the output list
            UseCase defaultUseCase = new UseCase()
            {
                Title = "No correlation rules were found for this log source.",
                Description = "---",
                Tags = new string[] { "null", "null" }
            };
            Logger.LogInformation("Attempt to find use cases for unknown log source:" + logsource); //Log Information
            outputUseCases.Add(defaultUseCase);
        }

        // Get the mapped use cases for each rule type
        foreach (var rt in RuleTypes)
        {
            //Retrieving a UseCase object from the data in database
            UseCase uc = new UseCase()
            {
                Title = rt.Type,
                Description = rt.Description,
                Tags = GetTags(rt.Type) //Tags are stored in another database table, so another query is necessary
            };
            outputUseCases.Add(uc);
        }

        Logger.LogInformation("Use cases mapped: {0}", JsonSerializer.Serialize(outputUseCases)); //Log output use cases

        return outputUseCases;
    }

    /// <summary>
    /// Gets the tags associated with a given title.
    /// </summary>
    /// <param name="title">The title to search for.</param>
    /// <returns>An array of strings representing the tags.</returns>
    private static string[] GetTags(string title)
    {
        // Join RuleTypes and RulesMetadataTypes with Rules based on their identifiers to get the metadata types for the given title.
        var result = Context.Rules
            .Join(Context.RuleTypes, r => r.RuleType.Identifier, rt => rt.Identifier,
                (r, rt) => new { RuleType = rt })
            .Join(Context.RulesMetadataTypes, rrt => rrt.RuleType.Identifier, rmt => rmt.Identifier,
                (rrt, rmt) => new { RuleType = rrt.RuleType, RulesMetadataType = rmt })
            .Where(rrt => rrt.RuleType.Type == title)
            .Select(rrt => rrt.RulesMetadataType.Type).FirstOrDefault();
        
        // Split the result string by spaces and commas, trim the resulting strings, and convert to an array of strings, because it is saved as a string in the database, but is used as a string[] in the rest of the application.
        string[] tags = result.Split(new char[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries)
            .Select(s => s.Trim())
            .ToArray();

        Logger.LogDebug("Tags mapped: " + tags); //Log
        
        return tags;
    }

    /// <summary>
    /// Gets a list of RuleTypes associated with a given log source.
    /// </summary>
    /// <param name="logsource">The log source to search for.</param>
    /// <returns>A list of RuleTypes associated with the given log source.</returns>
    private static List<RuleType> GetTitles(string logsource)
    {
        // Join RuleTypes and RulesMetadataTypes with Rules based on their identifiers to get the distinct RuleTypes for the given log source.
        List<RuleType> ruleTypes = Context.Rules
            .Join(Context.RuleTypes, r => r.RuleType.Identifier, rt => rt.Identifier,
                (r, rt) => new { Rule = r, RuleType = rt })
            .Join(Context.RulesMetadataTypes, rrt => rrt.Rule.Identifier, rmt => rmt.Identifier,
                (rrt, rmt) => new { Rule = rrt.Rule, RuleType = rrt.RuleType, RulesMetadataType = rmt })
            .Where(rrt => rrt.RulesMetadataType.LogSource == logsource)
            .Select(rrt => rrt.RuleType)
            .Distinct()
            .ToList();
        
        Logger.LogDebug("Titles mapped: " + JsonSerializer.Serialize(ruleTypes)); //Log

        return ruleTypes;
    }
}