using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;

namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Services;

public class TemplateFolderService
{
    private readonly ITemplateEngineRepository _repository;
    private readonly IAuditService _auditService;

    /// <summary>
    /// Initializes the service with its required dependencies.
    /// </summary>
    /// <param name="repository">Template engine repository for folder persistence.</param>
    /// <param name="auditService">Audit service for logging folder operations.</param>
    public TemplateFolderService(ITemplateEngineRepository repository, IAuditService auditService)
    {
        _repository = repository ?? throw new ArgumentNullException(nameof(repository));
        _auditService = auditService ?? throw new ArgumentNullException(nameof(auditService));
    }

    /// <summary>
    /// Creates a new template folder, enforcing name uniqueness within the parent.
    /// </summary>
    /// <param name="name">Display name for the new folder.</param>
    /// <param name="parentFolderId">Parent folder id, or null to create at root level.</param>
    /// <param name="user">Username of the actor performing the operation (for audit).</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>A task that resolves to the created <see cref="TemplateFolder"/>, or a failure result if validation fails.</returns>
    public async Task<Result<TemplateFolder>> CreateFolderAsync(
        string name, int? parentFolderId, string user,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(name))
            return Result<TemplateFolder>.Failure("Folder name is required.");

        if (parentFolderId.HasValue)
        {
            var parent = await _repository.GetFolderByIdAsync(parentFolderId.Value, cancellationToken);
            if (parent == null)
                return Result<TemplateFolder>.Failure($"Parent folder with ID {parentFolderId.Value} not found.");
        }

        var all = await _repository.GetAllFoldersAsync(cancellationToken);
        if (all.Any(f => f.ParentFolderId == parentFolderId
                         && string.Equals(f.Name, name, StringComparison.OrdinalIgnoreCase)))
            return Result<TemplateFolder>.Failure($"A folder named '{name}' already exists at this level.");

        var siblings = all.Where(f => f.ParentFolderId == parentFolderId).ToList();
        var sortOrder = siblings.Count == 0 ? 0 : siblings.Max(f => f.SortOrder) + 1;

        var folder = new TemplateFolder(name) { ParentFolderId = parentFolderId, SortOrder = sortOrder };
        await _repository.AddFolderAsync(folder, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);
        await _auditService.LogAsync(user, "Create", "TemplateFolder", folder.Id.ToString(), name, folder, cancellationToken);
        // The audit entry is staged on the change tracker by LogAsync and needs its
        // own SaveChangesAsync to persist (mirrors TemplateService) — otherwise the
        // row is discarded when the ManagementActor's DI scope is disposed.
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<TemplateFolder>.Success(folder);
    }

    /// <summary>
    /// Renames an existing template folder, enforcing name uniqueness within its parent.
    /// </summary>
    /// <param name="folderId">Id of the folder to rename.</param>
    /// <param name="newName">New display name for the folder.</param>
    /// <param name="user">Username of the actor performing the operation (for audit).</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>A task that resolves to the updated <see cref="TemplateFolder"/>, or a failure result if the folder is not found or the name collides.</returns>
    public async Task<Result<TemplateFolder>> RenameFolderAsync(
        int folderId, string newName, string user,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(newName))
            return Result<TemplateFolder>.Failure("Folder name is required.");

        var folder = await _repository.GetFolderByIdAsync(folderId, cancellationToken);
        if (folder == null)
            return Result<TemplateFolder>.Failure($"Folder with ID {folderId} not found.");

        var all = await _repository.GetAllFoldersAsync(cancellationToken);
        if (all.Any(f => f.Id != folderId
                         && f.ParentFolderId == folder.ParentFolderId
                         && string.Equals(f.Name, newName, StringComparison.OrdinalIgnoreCase)))
            return Result<TemplateFolder>.Failure($"A folder named '{newName}' already exists at this level.");

        folder.Name = newName;
        await _repository.UpdateFolderAsync(folder, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);
        await _auditService.LogAsync(user, "Update", "TemplateFolder", folder.Id.ToString(), newName, folder, cancellationToken);
        // Persist the staged audit entry (see CreateFolderAsync).
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<TemplateFolder>.Success(folder);
    }

    /// <summary>
    /// Moves an existing template folder to a new parent, checking for cycles and name collisions.
    /// </summary>
    /// <param name="folderId">Id of the folder to move.</param>
    /// <param name="newParentId">Target parent folder id, or null to move to root level.</param>
    /// <param name="user">Username of the actor performing the operation (for audit).</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>A task that resolves to the moved <see cref="TemplateFolder"/>, or a failure result if a cycle or name collision is detected.</returns>
    public async Task<Result<TemplateFolder>> MoveFolderAsync(
        int folderId, int? newParentId, string user,
        CancellationToken cancellationToken = default)
    {
        var folder = await _repository.GetFolderByIdAsync(folderId, cancellationToken);
        if (folder == null)
            return Result<TemplateFolder>.Failure($"Folder with ID {folderId} not found.");

        if (newParentId.HasValue)
        {
            if (newParentId.Value == folderId)
                return Result<TemplateFolder>.Failure("Cannot move a folder into itself (cycle).");

            var newParent = await _repository.GetFolderByIdAsync(newParentId.Value, cancellationToken);
            if (newParent == null)
                return Result<TemplateFolder>.Failure($"Target folder with ID {newParentId.Value} not found.");

            var all = await _repository.GetAllFoldersAsync(cancellationToken);
            var byId = all.ToDictionary(f => f.Id);
            var cursor = newParentId;
            // Walk up from newParentId — if we encounter folderId, the move would create a cycle.
            // Bound iterations by byId.Count to defensively terminate on a malformed graph.
            var iterations = 0;
            while (cursor.HasValue)
            {
                if (cursor.Value == folderId)
                    return Result<TemplateFolder>.Failure("Cannot move a folder under one of its descendants (cycle).");
                if (++iterations > byId.Count)
                    return Result<TemplateFolder>.Failure("Folder hierarchy contains a cycle; cannot determine ancestry.");
                cursor = byId.TryGetValue(cursor.Value, out var node) ? node.ParentFolderId : null;
            }

            // Sibling-name uniqueness in destination.
            if (all.Any(f => f.Id != folderId
                             && f.ParentFolderId == newParentId
                             && string.Equals(f.Name, folder.Name, StringComparison.OrdinalIgnoreCase)))
                return Result<TemplateFolder>.Failure($"A folder named '{folder.Name}' already exists in the target folder.");
        }
        else
        {
            var all = await _repository.GetAllFoldersAsync(cancellationToken);
            if (all.Any(f => f.Id != folderId
                             && f.ParentFolderId == null
                             && string.Equals(f.Name, folder.Name, StringComparison.OrdinalIgnoreCase)))
                return Result<TemplateFolder>.Failure($"A folder named '{folder.Name}' already exists at the root.");
        }

        folder.ParentFolderId = newParentId;
        await _repository.UpdateFolderAsync(folder, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);
        await _auditService.LogAsync(user, "Move", "TemplateFolder", folder.Id.ToString(), folder.Name, folder, cancellationToken);
        // Persist the staged audit entry (see CreateFolderAsync).
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<TemplateFolder>.Success(folder);
    }

    /// <summary>
    /// Reorders a template folder relative to its siblings under the same parent by
    /// swapping <see cref="TemplateFolder.SortOrder"/> with the adjacent sibling in the
    /// requested direction. Siblings are ordered ascending by <see cref="TemplateFolder.SortOrder"/>
    /// then <see cref="TemplateFolder.Name"/>; <see cref="ReorderDirection.Up"/> swaps with the
    /// previous sibling and <see cref="ReorderDirection.Down"/> with the next. Reordering past
    /// either end is a no-op (returns success without persisting or auditing).
    /// </summary>
    /// <param name="folderId">Id of the folder to reorder.</param>
    /// <param name="direction">Direction to move the folder within its sibling set.</param>
    /// <param name="user">Username of the actor performing the operation (for audit).</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>A task that resolves to the reordered <see cref="TemplateFolder"/>, or a failure result if the folder is not found.</returns>
    public async Task<Result<TemplateFolder>> ReorderFolderAsync(
        int folderId, ReorderDirection direction, string user,
        CancellationToken cancellationToken = default)
    {
        var folder = await _repository.GetFolderByIdAsync(folderId, cancellationToken);
        if (folder == null)
            return Result<TemplateFolder>.Failure($"Folder with ID {folderId} not found.");

        var all = await _repository.GetAllFoldersAsync(cancellationToken);

        // Deterministic sibling ordering: ascending SortOrder, then Name as tie-breaker.
        var siblings = all
            .Where(f => f.ParentFolderId == folder.ParentFolderId)
            .OrderBy(f => f.SortOrder)
            .ThenBy(f => f.Name, StringComparer.OrdinalIgnoreCase)
            .ToList();

        var index = siblings.FindIndex(f => f.Id == folderId);
        var adjacentIndex = direction == ReorderDirection.Up ? index - 1 : index + 1;

        // At the end of the sibling set in the requested direction: no-op.
        if (adjacentIndex < 0 || adjacentIndex >= siblings.Count)
            return Result<TemplateFolder>.Success(folder);

        var adjacent = siblings[adjacentIndex];

        // Swap sort order with the adjacent sibling.
        (folder.SortOrder, adjacent.SortOrder) = (adjacent.SortOrder, folder.SortOrder);

        await _repository.UpdateFolderAsync(folder, cancellationToken);
        await _repository.UpdateFolderAsync(adjacent, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);
        await _auditService.LogAsync(user, "Reorder", "TemplateFolder", folder.Id.ToString(), folder.Name, folder, cancellationToken);
        // Persist the staged audit entry (see CreateFolderAsync).
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<TemplateFolder>.Success(folder);
    }

    /// <summary>
    /// Deletes an empty template folder. Fails if the folder contains sub-folders or templates.
    /// </summary>
    /// <param name="folderId">Id of the folder to delete.</param>
    /// <param name="user">Username of the actor performing the operation (for audit).</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>A task that resolves to a success result if the folder was deleted, or a failure result if the folder is not found or not empty.</returns>
    public async Task<Result<bool>> DeleteFolderAsync(
        int folderId, string user,
        CancellationToken cancellationToken = default)
    {
        var folder = await _repository.GetFolderByIdAsync(folderId, cancellationToken);
        if (folder == null)
            return Result<bool>.Failure($"Folder with ID {folderId} not found.");

        var allFolders = await _repository.GetAllFoldersAsync(cancellationToken);
        var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);

        var childFolderCount = allFolders.Count(f => f.ParentFolderId == folderId);
        var childTemplateCount = allTemplates.Count(t => t.FolderId == folderId);

        if (childFolderCount > 0 || childTemplateCount > 0)
        {
            var parts = new List<string>();
            if (childTemplateCount > 0)
                parts.Add($"{childTemplateCount} template{(childTemplateCount == 1 ? "" : "s")}");
            if (childFolderCount > 0)
                parts.Add($"{childFolderCount} subfolder{(childFolderCount == 1 ? "" : "s")}");
            return Result<bool>.Failure(
                $"Cannot delete folder '{folder.Name}': it contains {string.Join(" and ", parts)}. " +
                "Move or delete contents first.");
        }

        await _repository.DeleteFolderAsync(folderId, cancellationToken);
        await _repository.SaveChangesAsync(cancellationToken);
        await _auditService.LogAsync(user, "Delete", "TemplateFolder", folderId.ToString(), folder.Name, null, cancellationToken);
        // Persist the staged audit entry (see CreateFolderAsync).
        await _repository.SaveChangesAsync(cancellationToken);

        return Result<bool>.Success(true);
    }
}
