using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using System;
using System.IO;
using System.Net.Http;
using Microsoft.Extensions.PlatformAbstractions;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ViewComponents;

namespace LanBackup.WebApp.Tests
{
  public class TestFixture<TStartup> : IDisposable
  {

    private const string SolutionName = "LanBackup.sln";

    private readonly TestServer _server;
    public HttpClient Client;

    public TestFixture() : this(Path.Combine("src"))
    {
    }

    protected TestFixture(string solutionRelativeTargetProjectParentDir)
    {
      var startupAssembly = typeof(TStartup).GetTypeInfo().Assembly;
      var contentRoot = GetProjectPath(solutionRelativeTargetProjectParentDir, startupAssembly);

      var builder = new WebHostBuilder()
                .UseContentRoot(contentRoot)
                .ConfigureServices(InitializeServices)
                .UseEnvironment("Development")
                .UseStartup(typeof(TStartup));

      _server = new TestServer(builder);

      Client = _server.CreateClient();
    }


    public void Dispose()
    {
      Client.Dispose();
      _server.Dispose();
    }

    protected virtual void InitializeServices(IServiceCollection services)
    {
      var startupAssembly = typeof(TStartup).GetTypeInfo().Assembly;

      // Inject a custom application part manager. Overrides AddMvcCore() because that uses TryAdd().
      var manager = new ApplicationPartManager();
      manager.ApplicationParts.Add(new AssemblyPart(startupAssembly));

      manager.FeatureProviders.Add(new ControllerFeatureProvider());
      manager.FeatureProviders.Add(new ViewComponentFeatureProvider());

      services.AddSingleton(manager);
    }
    
    /// <summary>
    /// Gets the full path to the target project path that we wish to test
    /// </summary>
    /// <param name="solutionRelativePath">
    /// The parent directory of the target project.
    /// e.g. src, samples, test, or test/Websites
    /// </param>
    /// <param name="startupAssembly">The target project's assembly.</param>
    /// <returns>The full path to the target project.</returns>
    private static string GetProjectPath(string solutionRelativePath, Assembly startupAssembly)
    {
      // Get name of the target project which we want to test
      var projectName = startupAssembly.GetName().Name;

      // Get currently executing test project path
      var applicationBasePath = PlatformServices.Default.Application.ApplicationBasePath;

      // Find the folder which contains the solution file. We then use this information to find the target
      // project which we want to test.
      var directoryInfo = new DirectoryInfo(applicationBasePath);
      do
      {
        var solutionFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, SolutionName));
        if (solutionFileInfo.Exists)
        {
          return Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath, projectName));
        }

        directoryInfo = directoryInfo.Parent;
      }
      while (directoryInfo.Parent != null);

      throw new Exception($"Solution root could not be located using application root {applicationBasePath}.");
    }

    
  }
}
