using Microsoft.Graph;
using Microsoft.Graph.Models;
using Microsoft.SharePoint.Client;
using Serilog;
using SharepointToolbox.Core.Helpers;
using SharepointToolbox.Core.Models;
using AuthGraphClientFactory = SharepointToolbox.Infrastructure.Auth.GraphClientFactory;
using SpGroup = Microsoft.SharePoint.Client.Group;

namespace SharepointToolbox.Services;

public class BulkMemberService : IBulkMemberService
{
    private readonly AuthGraphClientFactory _graphClientFactory;

    public BulkMemberService(AuthGraphClientFactory graphClientFactory)
    {
        _graphClientFactory = graphClientFactory;
    }

    public async Task<BulkOperationSummary<BulkMemberRow>> AddMembersAsync(
        ClientContext ctx,
        string clientId,
        IReadOnlyList<BulkMemberRow> rows,
        IProgress<OperationProgress> progress,
        CancellationToken ct)
    {
        return await BulkOperationRunner.RunAsync(
            rows,
            async (row, idx, token) =>
            {
                await AddSingleMemberAsync(ctx, clientId, row, progress, token);
            },
            progress,
            ct);
    }

    private async Task AddSingleMemberAsync(
        ClientContext ctx,
        string clientId,
        BulkMemberRow row,
        IProgress<OperationProgress> progress,
        CancellationToken ct)
    {
        var siteUrl = row.GroupUrl;
        if (string.IsNullOrWhiteSpace(siteUrl))
        {
            // Fallback: use the context URL + group name for classic SP group
            await AddToClassicGroupAsync(ctx, row.GroupName, row.Email, row.Role, progress, ct);
            return;
        }

        // Try Graph API first for M365 Groups
        try
        {
            var graphClient = await _graphClientFactory.CreateClientAsync(clientId, ct);

            // Resolve the group ID from the site URL
            var groupId = await ResolveGroupIdAsync(graphClient, siteUrl, ct);
            if (groupId != null)
            {
                await AddViaGraphAsync(graphClient, groupId, row.Email, row.Role, ct);
                Log.Information("Added {Email} to M365 group {Group} via Graph", row.Email, row.GroupName);
                return;
            }
        }
        catch (OperationCanceledException) { throw; }
        catch (Exception ex)
        {
            Log.Warning("Graph API failed for {GroupUrl}, falling back to CSOM: {Error}",
                siteUrl, ex.Message);
        }

        // CSOM fallback for classic SharePoint groups
        await AddToClassicGroupAsync(ctx, row.GroupName, row.Email, row.Role, progress, ct);
    }

    private static async Task AddViaGraphAsync(
        GraphServiceClient graphClient,
        string groupId,
        string email,
        string role,
        CancellationToken ct)
    {
        // Resolve user by email
        var user = await graphClient.Users[email].GetAsync(cancellationToken: ct);
        if (user?.Id == null)
            throw new InvalidOperationException($"User not found: {email}");

        var userRef = $"https://graph.microsoft.com/v1.0/directoryObjects/{user.Id}";

        if (role.Equals("Owner", StringComparison.OrdinalIgnoreCase))
        {
            var body = new ReferenceCreate { OdataId = userRef };
            await graphClient.Groups[groupId].Owners.Ref.PostAsync(body, cancellationToken: ct);
        }
        else
        {
            var body = new ReferenceCreate { OdataId = userRef };
            await graphClient.Groups[groupId].Members.Ref.PostAsync(body, cancellationToken: ct);
        }
    }

    private static async Task<string?> ResolveGroupIdAsync(
        GraphServiceClient graphClient,
        string siteUrl,
        CancellationToken ct)
    {
        try
        {
            // Parse site URL to get hostname and site path
            var uri = new Uri(siteUrl);
            var hostname = uri.Host;
            var sitePath = uri.AbsolutePath.TrimEnd('/');

            var site = await graphClient.Sites[$"{hostname}:{sitePath}"].GetAsync(cancellationToken: ct);
            if (site?.Id == null) return null;

            // Try to get the associated group
            // Site.Id format: "hostname,siteCollectionId,siteId"
            var parts = site.Id.Split(',');
            if (parts.Length >= 2)
            {
                try
                {
                    var groups = await graphClient.Groups
                        .GetAsync(r =>
                        {
                            r.QueryParameters.Filter = $"resourceProvisioningOptions/any(x:x eq 'Team')";
                            r.QueryParameters.Select = new[] { "id", "displayName", "resourceProvisioningOptions" };
                        }, cancellationToken: ct);

                    // Find group associated with this site
                    if (groups?.Value != null)
                    {
                        foreach (var group in groups.Value)
                        {
                            if (group.Id != null) return group.Id;
                        }
                    }
                }
                catch (OperationCanceledException) { throw; }
                catch (Exception ex) { Log.Debug("Group lookup not available for {SiteUrl}: {Error}", siteUrl, ex.Message); }
            }

            return null;
        }
        catch (OperationCanceledException) { throw; }
        catch (Exception ex)
        {
            Log.Debug("Could not resolve M365 group ID for {SiteUrl}: {Error}", siteUrl, ex.Message);
            return null;
        }
    }

    private static async Task AddToClassicGroupAsync(
        ClientContext ctx,
        string groupName,
        string email,
        string role,
        IProgress<OperationProgress> progress,
        CancellationToken ct)
    {
        var web = ctx.Web;
        var groups = web.SiteGroups;
        ctx.Load(groups);
        await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);

        Microsoft.SharePoint.Client.Group? targetGroup = null;
        foreach (var group in groups)
        {
            if (group.Title.Equals(groupName, StringComparison.OrdinalIgnoreCase))
            {
                targetGroup = group;
                break;
            }
        }

        if (targetGroup == null)
            throw new InvalidOperationException($"SharePoint group not found: {groupName}");

        var user = web.EnsureUser(email);
        ctx.Load(user);
        await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);

        targetGroup.Users.AddUser(user);
        await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);

        Log.Information("Added {Email} to classic SP group {Group}", email, groupName);
    }
}
