namespace OpenWarehouse.warehouse.api.Common.ItemTagManager;

public class ItemTagManager(ApplicationDbContext applicationDbContext, ILogger<ItemManager> logger) : IItemTagManager
{
    public async Task<bool> CreateTag(string name, Guid? parentId)
    {
        logger.LogDebug("Attempting to create a new tag with name {TagName} and parent ID {ParentId}", name, parentId);
        
        ItemCategoryTag? parent = null;

        if (parentId != null)
        {
            parent = await applicationDbContext.ItemCategoryTags
                .FirstOrDefaultAsync(q => q.Id == parentId);

            if (parent == null)
            {
                logger.LogWarning("Parent tag with ID {ParentId} not found.", parentId);
                return false;
            }
        }

        var newTag = new ItemCategoryTag()
        {
            Name = name,
            ParentTag = parent
        };

        applicationDbContext.ItemCategoryTags.Add(newTag);

        bool isSaved = (await applicationDbContext.SaveChangesAsync()) > 0;

        if (isSaved)
        {
            logger.LogInformation("Successfully created tag {Name} with ID {TagId}.", name, newTag.Id);
        }
        else
        {
            logger.LogError("Failed to save the new tag {Name}.", name);
        }

        return isSaved;
    }

    public async Task<ItemCategoryTag?> GetTagByName(string name)
    {
        logger.LogDebug("Fetching tags with name {TagName}.", name);
        
        var tags = await applicationDbContext.ItemCategoryTags
            .Where(tag => tag.Name != null && tag.Name.Equals(name)).FirstOrDefaultAsync();

        if (tags == null)
        {
            logger.LogWarning("No tags found with name {Name}.", name);
        }

        return tags;
    }

    public async Task<ItemCategoryTag?> GetTagById(Guid id)
    {
        logger.LogDebug("Fetching tag with ID {TagId}.", id);
        
        var tag = await applicationDbContext.ItemCategoryTags
            .Where(tag => tag.Id == id).FirstOrDefaultAsync();

        if (tag == null)
        {
            logger.LogWarning("No tag found with ID {Id}.", id);
        }

        return tag;
    }

    public async Task<ItemCategoryTag?> GetTagById(string id)
    {
        logger.LogDebug("Fetching tag with string ID {TagId}.", id);
        return await GetTagById(Guid.Parse(id));
    }

    public async Task<bool> DeleteTag(ItemCategoryTag tag)
    {
        logger.LogDebug("Attempting to delete tag with object reference {Tag}.", tag);
        return await DeleteTag(tag.Id);
    }

    public async Task<bool> DeleteTag(Guid id)
    {
        logger.LogDebug("Attempting to delete tag with ID {TagId}.", id);
        
        bool isDeleted = await applicationDbContext.ItemCategoryTags.Where(q => q.Id == id)
            .ExecuteDeleteAsync() > 0;

        if (isDeleted)
        {
            logger.LogInformation("Successfully deleted tag with ID {TagId}.", id);
        }
        else
        {
            logger.LogError("Failed to delete tag with ID {TagId}.", id);
        }

        return isDeleted;
    }

    public async Task<int> DeleteTagsByNames(List<Model.Item.ItemCategoryTag> names)
    {
        logger.LogDebug("Attempting to delete tags by names.");
        
        if (!names.Any())
        {
            logger.LogWarning("No tags provided for deletion.");
            return 0;
        }

        try
        {
            var res = 0;

            foreach (var tag in names)
            {
                logger.LogDebug("Deleting tag with name {TagName}.", tag.Name);
                res += await applicationDbContext.ItemCategoryTags
                    .Where(q => q.Name == tag.Name)
                    .ExecuteDeleteAsync();
            }

            logger.LogInformation("{Count} tags were successfully deleted.", res);
            return res;
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "An error occurred while deleting tags.");
            throw;
        }
    }

    public async Task<bool> DeleteTag(string id)
    {
        logger.LogDebug("Attempting to delete tag with string ID {TagId}.", id);
        return await DeleteTag(Guid.Parse(id));
    }

    public async Task<int> CreateTagTree(List<Model.Item.ItemCategoryTag> itemCategoryTags)
    {
        logger.LogDebug("Attempting to create a tag tree.");
        
        var result = 0;
        try
        {
            if (itemCategoryTags.All(q => q.ParentName != null))
            {
                logger.LogError("Tag tree must have at least one root node.");
                throw new InvalidOperationException("Tag tree must have at least one root node.");
            }

            var tempParent = new List<ItemCategoryTag>();

            while (itemCategoryTags.Count > 0)
            {
                var currentLevel = itemCategoryTags
                    .Where(q => q.ParentName == null || itemCategoryTags.All(qq => qq.Name != q.ParentName))
                    .ToList();

                itemCategoryTags.RemoveAll(q => currentLevel.Contains(q));

                foreach (var tag in currentLevel)
                {
                    logger.LogDebug("Processing tag {TagName} with parent {ParentName}.", tag.Name, tag.ParentName);
                    
                    var exists = await applicationDbContext.ItemCategoryTags
                        .AnyAsync(t => t.Name == tag.Name && t.ParentTag != null && (t.ParentTag.Name == tag.ParentName ||
                            (t.ParentTag == null && tag.ParentName == null)));

                    if (exists)
                    {
                        logger.LogInformation("Tag with name {TagName} already exists.", tag.Name);
                        continue;
                    }

                    ItemCategoryTag? parent = null;

                    if (tag.ParentName != null)
                    {
                        parent = await applicationDbContext.ItemCategoryTags
                            .Where(q => q.Name == tag.ParentName).FirstOrDefaultAsync();

                        if (parent == null)
                        {
                            if (tempParent.Any(q => q.Name == tag.ParentName))
                            {
                                parent = tempParent.FirstOrDefault(q => q.Name == tag.ParentName);
                            }
                            else
                            {
                                logger.LogWarning("Parent tag with name {ParentName} not found for child {Name}.",
                                    tag.ParentName, tag.Name);
                                continue;
                            }
                        }
                    }

                    var tagNew = new ItemCategoryTag()
                    {
                        Name = tag.Name,
                        ParentTag = parent
                    };

                    applicationDbContext.ItemCategoryTags.Add(tagNew);
                    tempParent.Add(tagNew);

                    logger.LogDebug("Added new tag {TagName} with parent {ParentName}.", tagNew.Name,
                        parent?.Name);
                }

                result += await applicationDbContext.SaveChangesAsync();
            }

            logger.LogInformation("Successfully created tag tree with {Changes} changes.", result);

            return result;
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "An error occurred while creating the tag tree.");
            throw;
        }
    }

    public async Task<bool> DeleteAllChild(Guid id)
    {
        logger.LogDebug("Attempting to delete all children for tag ID {TagId}.", id);
        
        var parentTag = await GetTagById(id);
        if (parentTag == null)
        {
            logger.LogWarning("Parent tag with ID {Id} not found.", id);
            return false;
        }

        var childTags = await applicationDbContext.ItemCategoryTags
            .Where(q => q.ParentTagId == id).ToListAsync();

        if (!childTags.Any())
        {
            logger.LogInformation("No child tags found for parent tag with ID {Id}.", id);
            return true;
        }

        applicationDbContext.ItemCategoryTags.RemoveRange(childTags);
        bool isDeleted = (await applicationDbContext.SaveChangesAsync()) > 0;

        if (isDeleted)
        {
            logger.LogInformation("Successfully deleted all child tags for parent tag with ID {Id}.", id);
        }
        else
        {
            logger.LogError("Failed to delete child tags for parent tag with ID {Id}.", id);
        }

        return isDeleted;
    }

    public async Task<bool> DeleteAllChild(string id)
    {
        logger.LogDebug("Attempting to delete all children for tag string ID {TagId}.", id);
        return await DeleteAllChild(Guid.Parse(id));
    }

    public async Task<ItemCategoryTag?> GetTagTree(string root)
    {
        logger.LogDebug("Fetching tag tree starting from root {RootName}.", root);
        
        var rootElement = await applicationDbContext.ItemCategoryTags
            .Where(q => q.Name != null && q.Name.Equals(root))
            .AsNoTracking()
            .FirstOrDefaultAsync();

        if (rootElement == null)
        {
            logger.LogWarning("Root tag with name {RootName} not found.", root);
            return null;
        }

        await LoadChildren(rootElement);

        return rootElement;
    }

    public async Task<List<ItemCategoryTag>> GetFullTagTree()
    {
        logger.LogDebug("Fetching the full tag tree.");
        
        var allTags = await applicationDbContext.ItemCategoryTags
            .AsNoTracking()
            .ToListAsync();

        var tagDictionary = allTags.ToDictionary(tag => tag.Id);

        var rootTags = new List<ItemCategoryTag>();

        foreach (var tag in allTags)
        {
            if (tag.ParentTagId == null)
            {
                rootTags.Add(tag);
            }
            else if (tagDictionary.TryGetValue(tag.ParentTagId.Value, out var parentTag))
            {
                if (parentTag.ListOfChildrenTag == null)
                {
                    parentTag.ListOfChildrenTag = new List<ItemCategoryTag>();
                }

                parentTag.ListOfChildrenTag.Add(tag);
            }
        }

        logger.LogInformation("Successfully fetched the full tag tree with {Count} root nodes.", rootTags.Count);

        return rootTags;
    }

    private async Task LoadChildren(ItemCategoryTag parentTag)
    {
        logger.LogDebug("Loading children for tag {TagName} with ID {TagId}.", parentTag.Name, parentTag.Id);
        
        parentTag.ListOfChildrenTag = await applicationDbContext.ItemCategoryTags
            .Where(child => child.ParentTagId == parentTag.Id)
            .AsNoTracking()
            .ToListAsync();

        foreach (var child in parentTag.ListOfChildrenTag)
        {
            await LoadChildren(child);
        }
    }
}
