using DataModel.Database;
using DataModel.Database.Common;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace Processor;

public static class TextElementProcessor
{
    // Load all text elements for one particular section:
    public static async Task<List<TextElement>> GetTextElements(Section section, string filterTerm)
    {
        await using var db = ProcessorMeta.ServiceProvider.GetRequiredService<DataContext>();
        
        if(string.IsNullOrWhiteSpace(filterTerm))
            return await db.TextElements.Where(n => n.Section == section).OrderBy(n => n.Name).ThenBy(n => n.Id).ToListAsync();
        else
            return await db.TextElements.Where(n => n.Section == section && n.Name.Contains(filterTerm)).OrderBy(n => n.Name).ThenBy(n => n.Id).ToListAsync();
    }
    
    // Load one text element by id:
    public static async Task<TextElement> LoadTextElement(int id)
    {
        await using var db = ProcessorMeta.ServiceProvider.GetRequiredService<DataContext>();
        return await db.TextElements.FirstAsync(n => n.Id == id);
    }

    // Adds a new text element:
    public static async Task<ProcessorResult<TextElement>> AddTextElement(string? currentSectionKey, string elementName, bool isMultiLine)
    {
        await using var db = ProcessorMeta.ServiceProvider.GetRequiredService<DataContext>();
        
        if(string.IsNullOrWhiteSpace(currentSectionKey))
            throw new ArgumentOutOfRangeException(nameof(currentSectionKey));
        
        var currentSection = await db.Sections.FirstOrDefaultAsync(n => n.DataKey == currentSectionKey);
        if (currentSection is null)
            throw new ArgumentOutOfRangeException(nameof(currentSectionKey));
        
        // Generate a code:
        var code = await Utils.GenerateCode(elementName, db.TextElements, (n, code) => n.Section == currentSection && n.Code == code);
        
        var textElement = new TextElement
        {
            Name = elementName,
            Code = code,
            Section = currentSection,
            IsMultiLine = isMultiLine,
        };
        
        // Add the new element to the database:
        await db.TextElements.AddAsync(textElement);

        try
        {
            // Save the changes:
            await db.SaveChangesAsync();
            return new ProcessorResult<TextElement>(textElement);
        }
        catch (DbUpdateException updateException)
        {
            return updateException.ToProcessorResult<TextElement>();
        }
    }
    
    // Updates the multi-line flag of a text element:
    public static async Task<ProcessorResult<TextElement>> UpdateTextElementState(int id, bool isMultiLine)
    {
        await using var db = ProcessorMeta.ServiceProvider.GetRequiredService<DataContext>();
        var textElement = await db.TextElements.FirstAsync(n => n.Id == id);
        textElement.IsMultiLine = isMultiLine;
        
        try
        {
            // Save the changes:
            await db.SaveChangesAsync();
            return new ProcessorResult<TextElement>(textElement);
        }
        catch (DbUpdateException updateException)
        {
            return updateException.ToProcessorResult<TextElement>();
        }
    }
    
    // Renames a text element:
    public static async Task<ProcessorResult<TextElement>> RenameTextElement(int id, string newName, bool isMultiLine)
    {
        await using var db = ProcessorMeta.ServiceProvider.GetRequiredService<DataContext>();
        var textElement = await db.TextElements.FirstAsync(n => n.Id == id);

        // Get the corresponding section:
        var section = (await db.TextElements.FirstAsync(n => n.Id == id)).Section;
        
        // Generate a code:
        var code = await Utils.GenerateCode(newName, db.TextElements, (n, code) => n.Section == section && n.Code == code);
        
        textElement.Name = newName;
        textElement.Code = code;
        textElement.IsMultiLine = isMultiLine;
        
        // Save the changes:
        try
        {
            await db.SaveChangesAsync();
            return new ProcessorResult<TextElement>(textElement);
        }
        catch (DbUpdateException updateException)
        {
            return updateException.ToProcessorResult<TextElement>();
        }
    }
    
    // Deletes a text element:
    public static async Task DeleteTextElement(int id)
    {
        await using var db = ProcessorMeta.ServiceProvider.GetRequiredService<DataContext>();
        var textElement = await db.TextElements.FirstAsync(n => n.Id == id);

        // Remove the element from the database:
        db.TextElements.Remove(textElement);
        
        try
        {
            // Save the changes:
            await db.SaveChangesAsync();
        }
        catch (DbUpdateException)
        {
        }
    }

    public static async Task<string> GetKey(int id)
    {
        await using var db = ProcessorMeta.ServiceProvider.GetRequiredService<DataContext>();
        var textElement = await db.TextElements.FirstAsync(n => n.Id == id);
        
        //
        // Build the path to the text element:
        //
        var textElementPath = new List<string>(textElement.Section.Depth + 2)
        {
            textElement.Code
        };
                    
        var parent = textElement.Section;
        while (parent is not null)
        {
            // Load the parent's parent section:
            await db.Entry(parent).Reference(n => n.Parent).LoadAsync();
            
            // Add the parent's key to the path:
            textElementPath.Add(parent.DataKey);
            
            // Go to parent's parent:
            parent = parent.Parent;
        }
        
        textElementPath.Add("I18N");
        
        // Write the path to the text element as key:
        return $"{string.Join(".", textElementPath.ReverseIt())}";
    }
}