using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Raven.Client.Documents;
using Raven.Client.Documents.Linq;
using Raven.Client.Exceptions.Documents.Indexes;

namespace zero.Routing;

public class RouteResolver : Routes, IRouteResolver
{
  protected IApplicationResolver AppResolver { get; set; }
  protected IZeroOptions Options { get; set; }


  public RouteResolver(IZeroContext context, IZeroStore store, ILogger<Routes> logger, IEnumerable<IRouteProvider> providers, IApplicationResolver appResolver) : base(context, store, logger, providers)
  {
    AppResolver = appResolver;
    Options = context.Options;
  }


  /// <inheritdoc />
  public async Task<IRouteModel> ResolveUrl(HttpContext context, Application application, string path)
  {
    IZeroDocumentSession session = Store.Session(application.Database);

    path = path.Length > 1 ? path.TrimEnd(PATH_SEPERATOR) : path;

    string[] pathParts = path.Trim(PATH_SEPERATOR).Split(PATH_SEPERATOR);
    string[] parts = new string[pathParts.Length];

    int min = parts.Length;
    foreach (string pathPart in pathParts)
    {
      for (int i = 0; i < min; i++)
      {
        parts[i] += PATH_SEPERATOR + pathPart;
      }
      min -= 1;
    }

    IList<Route> routes = null;

    try
    {
      routes = await session.Query<Route, Routes_ForResolver>()
        .Where(x => (!x.AllowSuffix && x.Url == path) || (x.AllowSuffix && x.Url.In(parts)))
        .Include("References[].Id")
        .Include("Dependencies")
        .ToListAsync();

      routes = routes.Where(x => !x.Param<bool>(PageRouteProvider.PAGE_IS_FOLDER)).ToList();
    }
    catch (IndexDoesNotExistException ex)
    {
      Logger.LogError(ex, "Indexes have not been created yet");
      return null;
    }

    Route route = routes.FirstOrDefault();

    // try to get the best matching path when multiple routes are found
    // our assumption is that the best path is those with the longest path parts separated by a slash
    // if we still get multiple routes with equal part counts then we prefer those which do not allow a suffix
    if (routes.Count > 1)
    {
      int maxPathParts = routes.Max(x => x.Url.Count(u => u == PATH_SEPERATOR));
      IEnumerable<Route> longestRoutes = routes.Where(x => maxPathParts == x.Url.Count(u => u == PATH_SEPERATOR)).OrderBy(x => x.AllowSuffix);

      if (longestRoutes.Count() > 1) 
      {
        Logger.LogWarning("Multiple routes {routes} were found for {path}", longestRoutes.Select(x => x.Id), path);
      }

      route = longestRoutes.FirstOrDefault();
    }

    if (route == null)
    {
      return null;
    }

    return await ResolveRouteInternal(new RouteResponse()
    {
      Route = route,
      App = application,
      HttpContext = context,
      Path = path
    });
  }


  /// <inheritdoc />
  public async Task<IRouteModel> ResolveUrl(HttpContext context)
  {
    if (!Options.Initialized || context.IsBackofficeRequest(Options.ZeroPath))
    {
      return null;
    }

    Application app = await AppResolver.Resolve(context, Context.BackofficeUser);
    string path = context.Request.Path;

    if (app == null)
    {
      return null;
    }

    return await ResolveUrl(context, app, path);
  }


  /// <inheritdoc />
  public IRouteEndpoint MapEndpoint(IRouteModel route)
  {
    if (TryGetProvider(route.Route.ProviderAlias, out IRouteProvider provider))
    {
      return provider.Map(GetContext(), route);
    }

    return null;
  }


  /// <summary>
  /// Call the provider which can resolve the route
  /// </summary>
  async Task<IRouteModel> ResolveRouteInternal(RouteResponse response)
  {
    if (TryGetProvider(response.Route.ProviderAlias, out IRouteProvider provider))
    {
      return await provider.Model(GetContext(), response);
    }

    return null;
  }
}


public interface IRouteResolver
{
  /// <summary>
  /// Resolve an URL from the specified app and the path
  /// </summary>
  Task<IRouteModel> ResolveUrl(HttpContext context, Application application, string path);

  /// <summary>
  /// Resolve an URL from an http context
  /// </summary>
  Task<IRouteModel> ResolveUrl(HttpContext context);

  /// <summary>
  /// Get endpoint the route maps to
  /// </summary>
  IRouteEndpoint MapEndpoint(IRouteModel route);
}