using Microsoft.SharePoint.Client;
using PnP.Framework.Sites;
using Serilog;
using SharepointToolbox.Core.Helpers;
using SharepointToolbox.Core.Models;
using ModelSiteTemplate = SharepointToolbox.Core.Models.SiteTemplate;

namespace SharepointToolbox.Services;

public class TemplateService : ITemplateService
{
    private static readonly HashSet<string> SystemListNames = new(StringComparer.OrdinalIgnoreCase)
    {
        "Style Library", "Form Templates", "Site Assets", "Site Pages",
        "Composed Looks", "Master Page Gallery", "Web Part Gallery",
        "Theme Gallery", "Solution Gallery", "List Template Gallery",
        "Converted Forms", "Customized Reports", "Content type publishing error log",
        "TaxonomyHiddenList", "appdata", "appfiles"
    };

    public async Task<ModelSiteTemplate> CaptureTemplateAsync(
        ClientContext ctx,
        SiteTemplateOptions options,
        IProgress<OperationProgress> progress,
        CancellationToken ct)
    {
        progress.Report(new OperationProgress(0, 0, "Loading site properties..."));

        var web = ctx.Web;
        ctx.Load(web, w => w.Title, w => w.Description, w => w.Language,
                      w => w.SiteLogoUrl, w => w.WebTemplate, w => w.Configuration,
                      w => w.ServerRelativeUrl);
        await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);

        var siteType = (web.WebTemplate == "GROUP" || web.WebTemplate == "GROUP#0")
            ? "Team" : "Communication";

        var template = new ModelSiteTemplate
        {
            Name = string.Empty, // caller sets this
            SourceUrl = ctx.Url,
            CapturedAt = DateTime.UtcNow,
            SiteType = siteType,
            Options = options,
        };

        // Capture settings
        if (options.CaptureSettings)
        {
            template.Settings = new TemplateSettings
            {
                Title = web.Title,
                Description = web.Description,
                Language = (int)web.Language,
            };
        }

        // Capture logo
        if (options.CaptureLogo)
        {
            template.Logo = new TemplateLogo { LogoUrl = web.SiteLogoUrl ?? string.Empty };
        }

        // Capture libraries and folders
        if (options.CaptureLibraries || options.CaptureFolders)
        {
            progress.Report(new OperationProgress(0, 0, "Enumerating libraries..."));
            var lists = ctx.LoadQuery(web.Lists
                .Include(l => l.Title, l => l.Hidden, l => l.BaseType, l => l.BaseTemplate, l => l.RootFolder)
                .Where(l => !l.Hidden));
            await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);

            var filteredLists = lists
                .Where(l => !SystemListNames.Contains(l.Title))
                .Where(l => l.BaseType == BaseType.DocumentLibrary || l.BaseType == BaseType.GenericList)
                .ToList();

            for (int i = 0; i < filteredLists.Count; i++)
            {
                ct.ThrowIfCancellationRequested();
                var list = filteredLists[i];
                progress.Report(new OperationProgress(i + 1, filteredLists.Count,
                    $"Capturing library: {list.Title}"));

                var libInfo = new TemplateLibraryInfo
                {
                    Name = list.Title,
                    BaseType = list.BaseType.ToString(),
                    BaseTemplate = (int)list.BaseTemplate,
                };

                if (options.CaptureFolders)
                {
                    ctx.Load(list.RootFolder, f => f.ServerRelativeUrl);
                    await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);
                    libInfo.Folders = await EnumerateLibraryFoldersAsync(ctx, list, ct);
                }

                template.Libraries.Add(libInfo);
            }
        }

        // Capture permission groups
        if (options.CapturePermissionGroups)
        {
            progress.Report(new OperationProgress(0, 0, "Capturing permission groups..."));
            var groups = web.SiteGroups;
            ctx.Load(groups, gs => gs.Include(
                g => g.Title, g => g.Description));
            await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);

            foreach (var group in groups)
            {
                ct.ThrowIfCancellationRequested();

                // Load role definitions for this group
                var roleAssignments = web.RoleAssignments;
                ctx.Load(roleAssignments, ras => ras.Include(
                    ra => ra.Member.LoginName,
                    ra => ra.Member.Title,
                    ra => ra.RoleDefinitionBindings.Include(rd => rd.Name)));
                await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);

                var roles = new List<string>();
                foreach (var ra in roleAssignments)
                {
                    if (ra.Member.Title == group.Title)
                    {
                        foreach (var rd in ra.RoleDefinitionBindings)
                        {
                            roles.Add(rd.Name);
                        }
                    }
                }

                template.PermissionGroups.Add(new TemplatePermissionGroup
                {
                    Name = group.Title,
                    Description = group.Description ?? string.Empty,
                    RoleDefinitions = roles,
                });
            }
        }

        progress.Report(new OperationProgress(1, 1, "Template capture complete."));
        return template;
    }

    public async Task<string> ApplyTemplateAsync(
        ClientContext adminCtx,
        ModelSiteTemplate template,
        string newSiteTitle,
        string newSiteAlias,
        IProgress<OperationProgress> progress,
        CancellationToken ct)
    {
        // 1. Create the site
        progress.Report(new OperationProgress(0, 0, $"Creating {template.SiteType} site: {newSiteTitle}..."));
        string siteUrl;

        if (template.SiteType.Equals("Team", StringComparison.OrdinalIgnoreCase))
        {
            var info = new TeamSiteCollectionCreationInformation
            {
                DisplayName = newSiteTitle,
                Alias = newSiteAlias,
                Description = template.Settings?.Description ?? string.Empty,
                IsPublic = false,
            };
            using var siteCtx = await adminCtx.CreateSiteAsync(info);
            siteCtx.Load(siteCtx.Web, w => w.Url);
            await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(siteCtx, progress, ct);
            siteUrl = siteCtx.Web.Url;
        }
        else
        {
            var tenantHost = new Uri(adminCtx.Url).Host;
            var info = new CommunicationSiteCollectionCreationInformation
            {
                Title = newSiteTitle,
                Url = $"https://{tenantHost}/sites/{newSiteAlias}",
                Description = template.Settings?.Description ?? string.Empty,
            };
            using var siteCtx = await adminCtx.CreateSiteAsync(info);
            siteCtx.Load(siteCtx.Web, w => w.Url);
            await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(siteCtx, progress, ct);
            siteUrl = siteCtx.Web.Url;
        }

        // 2. Connect to the new site and apply template structure
        // Need a new context for the created site
        var newCtx = new ClientContext(siteUrl);
        // Copy auth cookies/token from admin context
        newCtx.Credentials = adminCtx.Credentials;

        try
        {
            // Apply libraries
            if (template.Libraries.Count > 0)
            {
                for (int i = 0; i < template.Libraries.Count; i++)
                {
                    ct.ThrowIfCancellationRequested();
                    var lib = template.Libraries[i];
                    progress.Report(new OperationProgress(i + 1, template.Libraries.Count,
                        $"Creating library: {lib.Name}"));

                    try
                    {
                        var listInfo = new ListCreationInformation
                        {
                            Title = lib.Name,
                            TemplateType = lib.BaseTemplate,
                        };
                        var newList = newCtx.Web.Lists.Add(listInfo);
                        await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(newCtx, progress, ct);

                        // Create folders in the library
                        if (lib.Folders.Count > 0)
                        {
                            await CreateFoldersFromTemplateAsync(newCtx, newList, lib.Folders, progress, ct);
                        }
                    }
                    catch (Exception ex)
                    {
                        Log.Warning("Failed to create library {Name}: {Error}", lib.Name, ex.Message);
                    }
                }
            }

            // Apply permission groups
            if (template.PermissionGroups.Count > 0)
            {
                progress.Report(new OperationProgress(0, 0, "Creating permission groups..."));
                foreach (var group in template.PermissionGroups)
                {
                    ct.ThrowIfCancellationRequested();
                    try
                    {
                        var groupInfo = new GroupCreationInformation
                        {
                            Title = group.Name,
                            Description = group.Description,
                        };
                        var newGroup = newCtx.Web.SiteGroups.Add(groupInfo);

                        // Assign role definitions
                        foreach (var roleName in group.RoleDefinitions)
                        {
                            try
                            {
                                var roleDef = newCtx.Web.RoleDefinitions.GetByName(roleName);
                                var roleBindings = new RoleDefinitionBindingCollection(newCtx) { roleDef };
                                newCtx.Web.RoleAssignments.Add(newGroup, roleBindings);
                            }
                            catch (Exception ex)
                            {
                                Log.Warning("Failed to assign role {Role} to group {Group}: {Error}",
                                    roleName, group.Name, ex.Message);
                            }
                        }
                        await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(newCtx, progress, ct);
                    }
                    catch (Exception ex)
                    {
                        Log.Warning("Failed to create group {Name}: {Error}", group.Name, ex.Message);
                    }
                }
            }

            // Apply logo
            if (template.Logo != null && !string.IsNullOrEmpty(template.Logo.LogoUrl))
            {
                try
                {
                    newCtx.Web.SiteLogoUrl = template.Logo.LogoUrl;
                    newCtx.Web.Update();
                    await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(newCtx, progress, ct);
                }
                catch (Exception ex)
                {
                    Log.Warning("Failed to set site logo: {Error}", ex.Message);
                }
            }
        }
        finally
        {
            newCtx.Dispose();
        }

        progress.Report(new OperationProgress(1, 1, $"Template applied. Site created at: {siteUrl}"));
        return siteUrl;
    }

    /// <summary>
    /// Enumerates every folder in a library via one paginated CAML scan, then
    /// reconstructs the hierarchy from the server-relative paths. Replaces the
    /// former per-level Folder.Folders lazy loading, which hits the list-view
    /// threshold on libraries above 5,000 items.
    /// </summary>
    private static async Task<List<TemplateFolderInfo>> EnumerateLibraryFoldersAsync(
        ClientContext ctx,
        List list,
        CancellationToken ct)
    {
        ct.ThrowIfCancellationRequested();

        var rootUrl = list.RootFolder.ServerRelativeUrl.TrimEnd('/');

        // Collect all folders flat: (relativePath, parentRelativePath).
        var folders = new List<(string Relative, string Parent)>();

        await foreach (var item in SharePointPaginationHelper.GetItemsInFolderAsync(
            ctx, list, rootUrl, recursive: true,
            viewFields: new[] { "FSObjType", "FileLeafRef", "FileRef", "FileDirRef" },
            ct: ct))
        {
            if (item["FSObjType"]?.ToString() != "1") continue; // folders only

            var name    = item["FileLeafRef"]?.ToString() ?? string.Empty;
            var fileRef = (item["FileRef"]?.ToString() ?? string.Empty).TrimEnd('/');
            var dirRef  = (item["FileDirRef"]?.ToString() ?? string.Empty).TrimEnd('/');

            if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fileRef)) continue;
            if (name.StartsWith("_", StringComparison.Ordinal) ||
                name.Equals("Forms", StringComparison.OrdinalIgnoreCase))
                continue;

            // Paths relative to the library root.
            var rel = fileRef.StartsWith(rootUrl, StringComparison.OrdinalIgnoreCase)
                ? fileRef.Substring(rootUrl.Length).TrimStart('/')
                : name;
            var parentRel = dirRef.StartsWith(rootUrl, StringComparison.OrdinalIgnoreCase)
                ? dirRef.Substring(rootUrl.Length).TrimStart('/')
                : string.Empty;

            folders.Add((rel, parentRel));
        }

        // Build tree keyed by relative path.
        var nodes = folders.ToDictionary(
            f => f.Relative,
            f => new TemplateFolderInfo
            {
                Name = System.IO.Path.GetFileName(f.Relative),
                RelativePath = f.Relative,
                Children = new List<TemplateFolderInfo>(),
            },
            StringComparer.OrdinalIgnoreCase);

        var roots = new List<TemplateFolderInfo>();
        foreach (var (rel, parent) in folders)
        {
            if (!nodes.TryGetValue(rel, out var node)) continue;
            if (!string.IsNullOrEmpty(parent) && nodes.TryGetValue(parent, out var p))
                p.Children.Add(node);
            else
                roots.Add(node);
        }
        return roots;
    }

    private static async Task CreateFoldersFromTemplateAsync(
        ClientContext ctx,
        List list,
        List<TemplateFolderInfo> folders,
        IProgress<OperationProgress> progress,
        CancellationToken ct)
    {
        ctx.Load(list.RootFolder, f => f.ServerRelativeUrl);
        await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);
        var baseUrl = list.RootFolder.ServerRelativeUrl.TrimEnd('/');

        await CreateFoldersRecursiveAsync(ctx, baseUrl, folders, progress, ct);
    }

    private static async Task CreateFoldersRecursiveAsync(
        ClientContext ctx,
        string parentUrl,
        List<TemplateFolderInfo> folders,
        IProgress<OperationProgress> progress,
        CancellationToken ct)
    {
        foreach (var folder in folders)
        {
            ct.ThrowIfCancellationRequested();
            try
            {
                var folderUrl = $"{parentUrl}/{folder.Name}";
                ctx.Web.Folders.Add(folderUrl);
                await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);

                if (folder.Children.Count > 0)
                {
                    await CreateFoldersRecursiveAsync(ctx, folderUrl, folder.Children, progress, ct);
                }
            }
            catch (Exception ex)
            {
                Log.Warning("Failed to create folder {Path}: {Error}", folder.RelativePath, ex.Message);
            }
        }
    }
}
