// Copyright (c) 2026 Duplicati Inc. All rights reserved.

using Duplicati.Library.Common.IO;
using Duplicati.Library.Interface;
using Google.Apis.Gmail.v1;
using Google.Apis.Gmail.v1.Data;
using Google.Apis.Requests;
using System.Runtime.CompilerServices;

namespace Duplicati.Proprietary.GoogleWorkspace.SourceItems;

/// <summary>
/// Represents a folder containing conversations (emails) from a Google Group.
/// Uses the Gmail API to search for emails sent to the group address across all member mailboxes.
/// </summary>
internal class GroupConversationsSourceEntry(SourceProvider provider, string parentPath, string groupEmail)
    : MetaEntryBase(Util.AppendDirSeparator(SystemIO.IO_OS.PathCombine(parentPath, "conversations")), null, null)
{
    public override async IAsyncEnumerable<ISourceProviderEntry> Enumerate([EnumeratorCancellation] CancellationToken cancellationToken)
    {
        // Get group members to find one mailbox to search
        var memberEmails = await GetGroupMemberEmailsAsync(cancellationToken);

        // Pick the first member, or fall back to the admin's mailbox
        var userToSearch = memberEmails.Append("me").First();

        await foreach (var entry in EnumerateMessagesForUser(userToSearch, cancellationToken))
            yield return entry;
    }

    /// <summary>
    /// Gets the email addresses of all group members.
    /// </summary>
    private async Task<List<string>> GetGroupMemberEmailsAsync(CancellationToken cancellationToken)
    {
        var memberEmails = new List<string>();

        try
        {
            var service = provider.ApiHelper.GetDirectoryServiceForGroups();
            var request = service.Members.List(groupEmail);

            string? nextPageToken = null;
            do
            {
                if (cancellationToken.IsCancellationRequested) break;
                request.PageToken = nextPageToken;
                var response = await request.ExecuteAsync(cancellationToken);

                if (response.MembersValue != null)
                {
                    foreach (var member in response.MembersValue)
                    {
                        // Only include USER type members (not groups or other entity types)
                        // and members with an email address
                        if (member.Type == "USER" && !string.IsNullOrEmpty(member.Email))
                        {
                            memberEmails.Add(member.Email);
                        }
                    }
                }
                nextPageToken = response.NextPageToken;
            } while (!string.IsNullOrEmpty(nextPageToken));
        }
        catch (Exception)
        {
            // If we can't get members, we'll fall back to searching the admin's mailbox
        }

        return memberEmails;
    }

    /// <summary>
    /// Enumerates messages for a specific user that were sent to the group.
    /// </summary>
    private async IAsyncEnumerable<ISourceProviderEntry> EnumerateMessagesForUser(
        string userId,
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        GmailService service;
        try
        {
            service = provider.ApiHelper.GetGmailService(userId);
        }
        catch
        {
            // If we can't access this user's mailbox, skip them
            yield break;
        }

        // Search for emails sent to the group address
        var request = service.Users.Messages.List(userId);
        request.Q = $"to:{groupEmail} OR cc:{groupEmail}";

        do
        {
            if (cancellationToken.IsCancellationRequested)
                yield break;

            ListMessagesResponse response;
            try
            {
                response = await request.ExecuteAsync(cancellationToken);
            }
            catch
            {
                // If we can't search this user's mailbox, skip them
                yield break;
            }

            if (response.Messages != null)
            {
                var batch = new BatchRequest(service);
                var messages = new List<Message>();

                foreach (var msg in response.Messages)
                {
                    var msgRequest = service.Users.Messages.Get(userId, msg.Id);
                    msgRequest.Format = UsersResource.MessagesResource.GetRequest.FormatEnum.Metadata;
                    batch.Queue<Message>(msgRequest, (message, error, index, httpResponse) =>
                    {
                        if (error == null)
                        {
                            lock (messages)
                            {
                                messages.Add(message);
                            }
                        }
                    });
                }

                if (batch.Count > 0)
                {
                    await batch.ExecuteAsync(cancellationToken);
                }

                foreach (var message in messages)
                {
                    if (cancellationToken.IsCancellationRequested)
                        yield break;

                    yield return new GmailMessageSourceEntry(userId, this.Path, message, service, groupEmail);
                }
            }

            request.PageToken = response.NextPageToken;
        } while (!string.IsNullOrEmpty(request.PageToken));
    }

    public override Task<Dictionary<string, string?>> GetMinorMetadata(CancellationToken cancellationToken)
    {
        return Task.FromResult(new Dictionary<string, string?>
        {
            { "gsuite:v", "1" },
            { "gsuite:Type", SourceItemType.GroupConversations.ToString() },
            { "gsuite:Name", "conversations" },
            { "gsuite:Id", groupEmail }
        }
        .Where(kv => !string.IsNullOrEmpty(kv.Value))
        .ToDictionary(kv => kv.Key, kv => kv.Value));
    }
}