using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace POC_authentif_kestrel
{
  public class LoopbackHttpListener : IDisposable
  {
    const int DefaultTimeout = 60 * 5; // 5 mins (in seconds)

    IWebHost _host;
    TaskCompletionSource<string> _source = new TaskCompletionSource<string>();
    string _url;

    public string Url => _url;

    public LoopbackHttpListener(int port, string path = null)
    {
      path = path ?? String.Empty;
      if (path.StartsWith("/")) path = path.Substring(1);

      _url = $"https://localhost:{port}/{path}";

      _host = new WebHostBuilder()
          .UseKestrel()
          .UseUrls(_url)
          .Configure(Configure)
          .Build();
      _host.Start();
    }

    public void Dispose()
    {
      Task.Run(async () =>
      {
        await Task.Delay(500);
        _host.Dispose();
      });
    }

    void Configure(IApplicationBuilder app)
    {
      app.Run(async ctx =>
      {
        if (ctx.Request.Method == "GET")
        {
          await SetResultAsync(ctx.Request.QueryString.Value, ctx);
        }
        else
        {
          ctx.Response.StatusCode = 405;
        }
      });
    }

    private async Task SetResultAsync(string value, HttpContext ctx)
    {
      _source.TrySetResult(value);

      try
      {
        ctx.Response.StatusCode = 200;
        ctx.Response.ContentType = "text/html";
        ctx.Response.Headers.Add("Date", DateTimeOffset.UtcNow.ToString());
        await ctx.Response.WriteAsync("<h1>You can now return to the application.</h1>");
        await ctx.Response.Body.FlushAsync();
      }
      catch
      {
        ctx.Response.StatusCode = 400;
        ctx.Response.ContentType = "text/html";
        ctx.Response.Headers.Add("Date", DateTimeOffset.UtcNow.ToString());
        await ctx.Response.WriteAsync("<h1>Invalid request.</h1>");
        await ctx.Response.Body.FlushAsync();
      }
    }

    public Task<string> WaitForCallbackAsync(int timeoutInSeconds = DefaultTimeout)
    {
      Task.Run(async () =>
      {
        await Task.Delay(timeoutInSeconds * 1000);
        _source.TrySetCanceled();
      });

      return _source.Task;
    }
  }
}
