using SharepointToolbox.Core.Helpers;
using SharepointToolbox.Core.Models;

namespace SharepointToolbox.Services;

/// <summary>
/// Scans permissions across multiple sites via PermissionsService,
/// then filters and transforms results into user-centric UserAccessEntry records.
/// </summary>
public class UserAccessAuditService : IUserAccessAuditService
{
    private readonly IPermissionsService _permissionsService;

    private static readonly HashSet<string> HighPrivilegeLevels = new(StringComparer.OrdinalIgnoreCase)
    {
        "Full Control",
        "Site Collection Administrator"
    };

    public UserAccessAuditService(IPermissionsService permissionsService)
    {
        _permissionsService = permissionsService;
    }

    public async Task<IReadOnlyList<UserAccessEntry>> AuditUsersAsync(
        ISessionManager sessionManager,
        TenantProfile currentProfile,
        IReadOnlyList<string> targetUserLogins,
        IReadOnlyList<SiteInfo> sites,
        ScanOptions options,
        IProgress<OperationProgress> progress,
        CancellationToken ct,
        Func<string, CancellationToken, Task<bool>>? onAccessDenied = null)
    {
        // Normalize target logins for case-insensitive matching.
        // Users may be identified by email ("alice@contoso.com") or full claim
        // ("i:0#.f|membership|alice@contoso.com"), so we match on "contains".
        var targets = targetUserLogins
            .Select(l => l.Trim().ToLowerInvariant())
            .Where(l => l.Length > 0)
            .ToHashSet();

        if (targets.Count == 0)
            return Array.Empty<UserAccessEntry>();

        var allEntries = new List<UserAccessEntry>();

        for (int i = 0; i < sites.Count; i++)
        {
            ct.ThrowIfCancellationRequested();
            var site = sites[i];
            progress.Report(new OperationProgress(i, sites.Count,
                $"Scanning site {i + 1}/{sites.Count}: {site.Title}..."));

            var profile = new TenantProfile
            {
                TenantUrl = site.Url,
                ClientId = currentProfile.ClientId,
                Name = site.Title
            };

            var ctx = await sessionManager.GetOrCreateContextAsync(profile, ct);
            IReadOnlyList<PermissionEntry> permEntries;
            try
            {
                permEntries = await _permissionsService.ScanSiteAsync(ctx, options, progress, ct);
            }
            catch (Microsoft.SharePoint.Client.ServerUnauthorizedAccessException) when (onAccessDenied != null)
            {
                var elevated = await onAccessDenied(site.Url, ct);
                if (!elevated)
                    throw;
                var retryCtx = await sessionManager.GetOrCreateContextAsync(profile, ct);
                permEntries = await _permissionsService.ScanSiteAsync(retryCtx, options, progress, ct);
            }

            allEntries.AddRange(TransformEntries(permEntries, targets, site));
        }

        progress.Report(new OperationProgress(sites.Count, sites.Count,
            $"Audit complete: {allEntries.Count} access entries found."));

        return allEntries;
    }

    /// <summary>
    /// Transforms PermissionEntry list into UserAccessEntry list,
    /// filtering to only entries that match target user logins.
    /// </summary>
    private static IEnumerable<UserAccessEntry> TransformEntries(
        IReadOnlyList<PermissionEntry> permEntries,
        HashSet<string> targets,
        SiteInfo site)
    {
        foreach (var entry in permEntries)
        {
            // Split semicolon-delimited Users and UserLogins
            var logins = entry.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries);
            var names = entry.Users.Split(';', StringSplitOptions.RemoveEmptyEntries);

            // Split semicolon-delimited PermissionLevels
            var permLevels = entry.PermissionLevels.Split(';', StringSplitOptions.RemoveEmptyEntries);

            for (int u = 0; u < logins.Length; u++)
            {
                var login = logins[u].Trim();
                var loginLower = login.ToLowerInvariant();
                var displayName = u < names.Length ? names[u].Trim() : login;

                // Check if this login matches any target user.
                // Match by "contains" because SharePoint claims may wrap the email:
                //   "i:0#.f|membership|alice@contoso.com" contains "alice@contoso.com"
                bool isTarget = targets.Any(t =>
                    loginLower.Contains(t) || t.Contains(loginLower));

                if (!isTarget) continue;

                // Determine access type
                var accessType = ClassifyAccessType(entry);

                // Emit one UserAccessEntry per permission level
                foreach (var level in permLevels)
                {
                    var trimmedLevel = level.Trim();
                    if (string.IsNullOrEmpty(trimmedLevel)) continue;

                    yield return new UserAccessEntry(
                        UserDisplayName: displayName,
                        UserLogin: StripClaimsPrefix(login),
                        SiteUrl: site.Url,
                        SiteTitle: site.Title,
                        ObjectType: entry.ObjectType,
                        ObjectTitle: entry.Title,
                        ObjectUrl: entry.Url,
                        PermissionLevel: trimmedLevel,
                        AccessType: accessType,
                        GrantedThrough: entry.GrantedThrough,
                        IsHighPrivilege: HighPrivilegeLevels.Contains(trimmedLevel),
                        IsExternalUser: PermissionEntryHelper.IsExternalUser(login),
                        TargetUrl: entry.TargetUrl,
                        TargetLabel: entry.TargetLabel,
                        SharingLinkType: entry.SharingLinkType);
                }
            }
        }
    }

    /// <summary>
    /// Classifies a PermissionEntry into Direct, Group, or Inherited access type.
    /// </summary>
    /// <summary>
    /// Removes SharePoint claims prefixes like "i:0#.f|membership|" so the
    /// UI displays a plain email instead of the full claim string.
    /// </summary>
    private static string StripClaimsPrefix(string login)
    {
        int pipe = login.LastIndexOf('|');
        return pipe >= 0 ? login[(pipe + 1)..] : login;
    }

    private static AccessType ClassifyAccessType(PermissionEntry entry)
    {
        // Inherited: object does not have unique permissions
        if (!entry.HasUniquePermissions)
            return AccessType.Inherited;

        // Group: GrantedThrough starts with "SharePoint Group:"
        if (entry.GrantedThrough.StartsWith("SharePoint Group:", StringComparison.OrdinalIgnoreCase))
            return AccessType.Group;

        // Direct: unique permissions, granted directly
        return AccessType.Direct;
    }
}
