using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using RudderSdk.Core;
using UnityEngine.Networking;

namespace RudderSdk.Unity
{
  public sealed class UnityWebRequestExecutor : IUnityRequestExecutor
  {
    private const int DefaultTimeoutSeconds = 10;

    private readonly int _timeoutSeconds;

    public UnityWebRequestExecutor(int timeoutSeconds = DefaultTimeoutSeconds)
    {
      _timeoutSeconds = timeoutSeconds > 0 ? timeoutSeconds : DefaultTimeoutSeconds;
    }

    public async Task<string> SendAsync(
      string method,
      string url,
      string body,
      IReadOnlyDictionary<string, string> headers,
      CancellationToken cancellationToken = default)
    {
      using (var request = new UnityWebRequest(url, method))
      {
        if (!string.IsNullOrEmpty(body))
        {
          var payload = System.Text.Encoding.UTF8.GetBytes(body);
          request.uploadHandler = new UploadHandlerRaw(payload);
        }

        request.downloadHandler = new DownloadHandlerBuffer();
        request.timeout = _timeoutSeconds;

        foreach (var header in headers)
        {
          request.SetRequestHeader(header.Key, header.Value);
        }

        var operation = request.SendWebRequest();
        var completion = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
        operation.completed += _ => completion.TrySetResult(true);

        using (cancellationToken.Register(() => completion.TrySetCanceled(cancellationToken)))
        {
          await completion.Task;
        }

        if (request.result == UnityWebRequest.Result.ConnectionError)
        {
          throw new RudderNetworkException(request.error);
        }

        if (request.result == UnityWebRequest.Result.ProtocolError)
        {
          var responseBody = request.downloadHandler.text;
          throw new UnityRequestException(
            (int)request.responseCode,
            responseBody,
            request.error + ": " + responseBody);
        }

        return request.downloadHandler?.text ?? string.Empty;
      }
    }
  }
}
