// ApiCalls.cs

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace Beam.Api;
    /// <summary>
    /// Executes a batch of <see cref="ApiCall"/>s using either sequential or parallel strategy.
    /// </summary>
    public sealed class ApiCalls {
        private readonly IReadOnlyList<ApiCall> _calls;
        private readonly int _maxDegree;

        internal ApiCalls(IReadOnlyList<ApiCall> calls, int? maxDegree) {
            _calls = calls ?? throw new ArgumentNullException(nameof(calls));
            _maxDegree = Math.Max(1, maxDegree ?? 1);
        }

        /// <summary>
        /// Runs every call and returns the ordered list of <see cref="ApiResponse"/>s.
        /// </summary>
        public async Task<IReadOnlyList<ApiResponse>> ExecuteAsync(
            ILogger<ApiResponse>? logger = null,
            (int @try, int max)? tries = null,
            CancellationToken ct = default) {
            if (_maxDegree == 1) {
                // sequential
                var sequential = new List<ApiResponse>(_calls.Count);
                foreach (var call in _calls)
                    sequential.Add(await call.GetResponse(logger, tries, ct));
                return sequential;
            }

            // parallel
            var bag = new ConcurrentBag<(int idx, ApiResponse res)>();
            await Parallel.ForEachAsync(
                _calls.Select((c, i) => (call: c, idx: i)),
                new ParallelOptions { MaxDegreeOfParallelism = _maxDegree, CancellationToken = ct },
                async (item, token) => {
                    var response = await item.call.GetResponse(logger, tries, token);
                    bag.Add((item.idx, response));
                });

            // keep original ordering
            return bag.OrderBy(x => x.idx).Select(x => x.res).ToList();
        }
    }

