using System.Text;
using ms_namingservice.Database;
using ms_namingservice.Mapping.ValuesMapping.TempMapping;
using ms_namingservice.Objects;
using ms_namingservice.Objects.SavedData;

namespace ms_namingservice.Mapping.ValuesMapping;

/// <summary>
/// Class handling with method for generated name mapping.
/// </summary>
public class GeneratedNameMapping
{
    /// <summary>
    /// Framework for generated name.
    /// </summary>
    private static string Framework;

    /// <summary>
    /// Represents the logger instance for this class.
    /// </summary>
    private static ILogger Logger = Logging.LoggerFactory.CreateLogger<GeneratedNameMapping>();

    /// <summary>
    /// Returns the generated name for a log source based on correlation rules.
    /// </summary>
    /// <param name="framework">The framework to use for the correlation rules naming.</param>
    /// <returns>The generated name for the log source.</returns>
    public static async Task<string> GetGeneratedName(string framework)
    {
        if (InputData.Input.Title.Equals("No correlation rules were found for this log source."))
        {
            return "null-null_null-null-null"; //For all the inserted log sources, that don`t have any correlation rules
        }
        Framework = framework;
        string generatedName = await GenerateFinalName(await GetLesTypeShort(), await GetLesNameShort(), await GetCategory(), await GetSubCategory(),
           await GetUseCaseName()); //Creating a generated name in its right look
        Logger.LogInformation("Generated name created: " + generatedName);
        return generatedName;
    }

    /// <summary>
    /// Generates the final name for a log source based on its type, name, category, subcategory, and use case name.
    /// </summary>
    /// <param name="type">The short type of the log source.</param>
    /// <param name="name">The short name of the log source.</param>
    /// <param name="category">The short category of the correlation rule.</param>
    /// <param name="subCategory">The short subcategory of the correlation rule.</param>
    /// <param name="useCaseName">The use case name of the log source.</param>
    /// <returns>The generated name for the log source.</returns>
    private static async Task<string> GenerateFinalName(string type, string name, string category, string subCategory,
        string useCaseName)
    {
        const char partSeparator = '_';
        const char inPartSeparator = '-';
        var generatedName = type + inPartSeparator + name + partSeparator + category + inPartSeparator + subCategory + partSeparator +
                            useCaseName;
        return generatedName;
    }

    /// <summary>
    /// Gets the short type of the log source based on the rules query.
    /// </summary>
    /// <returns>The short type of the log source.</returns>
    private static async Task<string> GetLesTypeShort()
    {
        string type = await RulesQuery.GetLesShortType(InputData.Input.LogSource.Type); //Database query
        //Returning the oth value, if no right value is matched in the query
        if (type != Const.OSShort && type != Const.SAShort && type != Const.OAShort && type != Const.NDShort)
        {
            Logger.LogDebug("Category of log source oth returned.");
            return "oth"; //Should not happen usually
        }

        return type;
    }

    /// <summary>
    /// Gets the short name of the log source based on the rules query.
    /// </summary>
    /// <returns>The short name of the log source.</returns>
    private static async Task<string> GetLesNameShort()
    {
        string name = await RulesQuery.GetLesShortName(InputData.Input.LogSource.Name); //Database query
        //Returning the oth value, if no right value is matched in the query
        if (name == null || name == String.Empty)
        {
            Logger.LogDebug("Short name of log source oth returned.");
            return "oth"; 
        }

        return name;
    }

    /// <summary>
    /// Gets the use case name by calling the RemoveSpecialCharacters method with InputData.Input.Title. 
    /// Then, converts the resulting string to lowercase and replaces spaces, double underscores, and periods with underscores. 
    /// If the resulting string ends with an underscore, it removes it. Returns the resulting use case name.
    /// </summary>
    /// <returns>The resulting use case name as a string.</returns>
    private static async Task<string> GetUseCaseName()
    {
        var name = await RemoveSpecialCharacters(InputData.Input.Title); //Using a input title of use case from the input object and deleting special characters
        name = name.ToLower().Replace(' ', '_').Replace("__", "_").Replace('.', '_'); //Replacing spaces with underline, deleting double spaces, replacing dots with underline
        
        //Deleting a space on the end of the word
        if (name.EndsWith('_')) 
        {
            name = name.Substring(0, name.Length - 1); 
        }
        
        return name;
    }

    /// <summary>
    /// Gets the category of the use case from the database, depending on the framework. It gets the category from the database based on the tag.
    /// </summary>
    /// <returns>The resulting category as a string.</returns>
    private static async Task<string> GetCategory()
    {
        //Checking, if the category can be recognized from the tags. If not, returning oth
        if (InputData.Input.Tags == null || InputData.Input.Tags[0].Equals("null") || InputData.Input.Tags[0].StartsWith("attack.t"))
        {
            Logger.LogDebug("Category of correlation rule oth returned.");
            return "oth";
        }
        
        //Getting category from the database
        var category = string.Empty;
        var mitreCategory = await RulesQuery.GetCategory(InputData.Input.Tags[0], Const.Mitre);
        var enisaCategory = await MitreToDefaultConvert.GetEnisaCategory(mitreCategory); //Converting from MITRE
        
        //Getting desired category
        if (Framework.Equals(Const.Mitre))
        {
            category = await RulesQuery.GetCategoryShort(InputData.Input.Tags[0], Const.Mitre);
        }
        else if (Framework.Equals(Const.Enisa))
        {
            category = await RulesQuery.GetCategoryShort(enisaCategory, Const.Enisa);
        }
        return category;
    }

    /// <summary>
    /// Gets the subcategory of the use case from the database, depending on the framework. It gets the subcategory from the database based on its tags.
    /// </summary>
    /// <returns>The resulting subcategory as a string.</returns>
    private static async Task<string> GetSubCategory()
    {
        string subCategory = string.Empty;
        
        //MITRE subcategory mapping
        if (Framework.Equals(Const.Mitre))
        {
            try
            {
                //Checking, if the subcategory can be recognised from the tags. If not, returning oth.
                if (InputData.Input.Tags[1].Equals("null"))
                {
                    Logger.LogDebug("Subcategory of correlation rule oth returned."); //Log
                    return "oth";
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex.Message); //Log error
            }

            //Checking, if the Tags.Length has at least two tags, so the subcategory can be recognized
            if (InputData.Input.Tags.Length > 1)
            { 
                subCategory = await RulesQuery.GetSubcategoryShort(InputData.Input.Tags[1], Const.Mitre); //Database query
            }
            else
            {
                Logger.LogDebug("Subcategory of correlation rule oth returned."); //Log
                subCategory = "oth";  //Returning oth value, because it cannot be recognised from the database.
            }
        } else if (Framework.Equals(Const.Enisa))
        {
            subCategory = "null"; //ENISA does not use subcategories
        }
        return subCategory;
    }
    
    /// <summary>
    /// Removes special characters from the input string, converts it to lowercase, and replaces spaces, double underscores, and periods with underscores. 
    /// If the resulting string ends with an underscore, it removes it. Returns the resulting string.
    /// </summary>
    /// <param name="str">The input string to process.</param>
    /// <returns>The resulting string with special characters removed and spaces, double underscores, and periods replaced with underscores.</returns>
    private static async Task<string> RemoveSpecialCharacters(string str)
    {
        var sb = new StringBuilder(); 
        //Checking all chars, if they can fit into the generated name correctly
        foreach (var c in str)
        {
            if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '.' || c == '_' ||
                c == ' ')
            {
                sb.Append(c); 
            }
        }

        return sb.ToString();
    }
}