using Microsoft.Extensions.Logging;
using Microsoft.SharePoint.Client;
using SharepointToolbox.Core.Helpers;
using SharepointToolbox.Core.Models;

namespace SharepointToolbox.Services;

public class VersionCleanupService : IVersionCleanupService
{
    private readonly ILogger<VersionCleanupService> _logger;

    public VersionCleanupService(ILogger<VersionCleanupService> logger)
    {
        _logger = logger;
    }

    public async Task<IReadOnlyList<string>> ListLibraryTitlesAsync(
        ClientContext ctx,
        CancellationToken ct)
    {
        ct.ThrowIfCancellationRequested();
        ctx.Load(ctx.Web,
            w => w.Lists.Include(
                l => l.Title,
                l => l.Hidden,
                l => l.BaseType));
        await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, null, ct);
        return ctx.Web.Lists
            .Where(l => !l.Hidden && l.BaseType == BaseType.DocumentLibrary)
            .Select(l => l.Title)
            .OrderBy(t => t, StringComparer.OrdinalIgnoreCase)
            .ToList();
    }

    public async Task<IReadOnlyList<VersionCleanupResult>> DeleteOldVersionsAsync(
        ClientContext ctx,
        VersionCleanupOptions options,
        IProgress<OperationProgress> progress,
        CancellationToken ct)
    {
        ct.ThrowIfCancellationRequested();

        if (options.KeepLast < 0)
            throw new ArgumentOutOfRangeException(nameof(options), "KeepLast must be >= 0.");

        ctx.Load(ctx.Web, w => w.Url, w => w.ServerRelativeUrl,
            w => w.Lists.Include(
                l => l.Title,
                l => l.Hidden,
                l => l.BaseType,
                l => l.RootFolder.ServerRelativeUrl));
        await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);

        var allLibs = ctx.Web.Lists
            .Where(l => !l.Hidden && l.BaseType == BaseType.DocumentLibrary)
            .ToList();

        var titleFilter = options.LibraryTitles?.Count > 0
            ? new HashSet<string>(options.LibraryTitles, StringComparer.OrdinalIgnoreCase)
            : null;

        var libs = titleFilter is null
            ? allLibs
            : allLibs.Where(l => titleFilter.Contains(l.Title)).ToList();

        var results = new List<VersionCleanupResult>();
        var siteUrl = ctx.Web.Url;

        int libIdx = 0;
        foreach (var lib in libs)
        {
            ct.ThrowIfCancellationRequested();
            libIdx++;
            progress.Report(new OperationProgress(libIdx, libs.Count,
                $"Scanning versions: {lib.Title} ({libIdx}/{libs.Count})"));

            // Enumerate files via paginated CAML so libs > 5,000 items work.
            var files = new List<string>();
            await foreach (var item in SharePointPaginationHelper.GetItemsInFolderAsync(
                ctx, lib, lib.RootFolder.ServerRelativeUrl, recursive: true,
                viewFields: new[] { "FSObjType", "FileRef" },
                ct: ct))
            {
                if (item["FSObjType"]?.ToString() != "0") continue;
                var fileRef = item["FileRef"]?.ToString();
                if (!string.IsNullOrEmpty(fileRef))
                    files.Add(fileRef);
            }

            int fileIdx = 0;
            foreach (var fileRef in files)
            {
                ct.ThrowIfCancellationRequested();
                fileIdx++;
                if (fileIdx % 25 == 0 || fileIdx == files.Count)
                {
                    progress.Report(new OperationProgress(fileIdx, files.Count,
                        $"{lib.Title}: {fileIdx}/{files.Count} files"));
                }

                var result = await TrimFileVersionsAsync(
                    ctx, siteUrl, lib.Title, fileRef, options, progress, ct);
                if (result is not null)
                    results.Add(result);
            }
        }

        return results;
    }

    private async Task<VersionCleanupResult?> TrimFileVersionsAsync(
        ClientContext ctx,
        string siteUrl,
        string libraryTitle,
        string fileServerRelativeUrl,
        VersionCleanupOptions options,
        IProgress<OperationProgress> progress,
        CancellationToken ct)
    {
        int before = 0;
        try
        {
            var file = ctx.Web.GetFileByServerRelativeUrl(fileServerRelativeUrl);
            ctx.Load(file, f => f.Name);
            ctx.Load(file.Versions,
                vs => vs.Include(
                    v => v.VersionLabel,
                    v => v.Created,
                    v => v.Size));
            await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);

            // file.Versions contains only HISTORICAL versions; the current published
            // version lives on `file` itself and is never deletable here.
            var versions = file.Versions.ToList();
            before = versions.Count;
            if (before == 0) return null;

            // Sort by Created ascending so [0] is the oldest historical version.
            var ordered = versions
                .OrderBy(v => v.Created)
                .ToList();

            // Preserve set: the last N most recent + optionally the very first.
            var keep = new HashSet<int>();
            int keepLast = Math.Min(options.KeepLast, ordered.Count);
            for (int i = ordered.Count - keepLast; i < ordered.Count; i++)
                keep.Add(i);
            if (options.KeepFirst && ordered.Count > 0)
                keep.Add(0);

            long bytesFreed = 0;
            int deleted = 0;
            for (int i = 0; i < ordered.Count; i++)
            {
                if (keep.Contains(i)) continue;
                var v = ordered[i];
                bytesFreed += v.Size;
                v.DeleteObject();
                deleted++;
            }

            if (deleted == 0) return null;
            await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);

            return new VersionCleanupResult
            {
                SiteUrl = siteUrl,
                Library = libraryTitle,
                FileServerRelativeUrl = fileServerRelativeUrl,
                FileName = System.IO.Path.GetFileName(fileServerRelativeUrl),
                VersionsBefore = before,
                VersionsDeleted = deleted,
                VersionsRemaining = before - deleted,
                BytesFreed = bytesFreed,
            };
        }
        catch (OperationCanceledException) { throw; }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to trim versions for {File}", fileServerRelativeUrl);
            return new VersionCleanupResult
            {
                SiteUrl = siteUrl,
                Library = libraryTitle,
                FileServerRelativeUrl = fileServerRelativeUrl,
                FileName = System.IO.Path.GetFileName(fileServerRelativeUrl),
                VersionsBefore = before,
                Error = ex.Message,
            };
        }
    }
}
