import 'dart:async';
import 'package:platform_container/container.dart';
import 'package:platform_routing/route.dart';
import 'package:logging/logging.dart';
import 'env.dart';
import 'hostname_parser.dart';
import 'request_context.dart';
import 'response_context.dart';
import 'routable.dart';
import 'server.dart';

/// A utility that allows requests to be handled based on their
/// origin's hostname.
///
/// For example, an application could handle example.com and api.example.com
/// separately.
///
/// The provided patterns can be any `Pattern`. If a `String` is provided, a simple
/// grammar (see [HostnameSyntaxParser]) is used to create [RegExp].
///
/// For example:
/// * `example.com` -> `/example\.com/`
/// * `*.example.com` -> `/([^$.]\.)?example\.com/`
/// * `example.*` -> `/example\./[^$]*`
/// * `example.+` -> `/example\./[^$]+`
class HostnameRouter {
  final Map<Pattern, Application> _apps = {};
  final Map<Pattern, FutureOr<Application> Function()> _creators = {};
  final List<Pattern> _patterns = [];

  HostnameRouter(
      {Map<Pattern, Application> apps = const {},
      Map<Pattern, FutureOr<Application> Function()> creators = const {}}) {
    Map<Pattern, V> parseMap<V>(Map<Pattern, V> map) {
      return map.map((p, c) {
        Pattern pp;

        if (p is String) {
          pp = HostnameSyntaxParser(p).parse();
        } else {
          pp = p;
        }

        return MapEntry(pp, c);
      });
    }

    apps = parseMap(apps);
    creators = parseMap(creators);
    var patterns = apps.keys.followedBy(creators.keys).toSet().toList();
    _apps.addAll(apps);
    _creators.addAll(creators);
    _patterns.addAll(patterns);
    // print(_creators);
  }

  factory HostnameRouter.configure(
      Map<Pattern, FutureOr<void> Function(Application)> configurers,
      {Reflector reflector = const EmptyReflector(),
      ProtevusEnvironment environment = protevusEnv,
      Logger? logger,
      bool allowMethodOverrides = true,
      FutureOr<String> Function(dynamic)? serializer,
      ViewGenerator? viewGenerator}) {
    var creators = configurers.map((p, c) {
      return MapEntry(p, () async {
        var app = Application(
            reflector: reflector,
            environment: environment,
            logger: logger,
            allowMethodOverrides: allowMethodOverrides,
            serializer: serializer,
            viewGenerator: viewGenerator);
        await app.configure(c);
        return app;
      });
    });
    return HostnameRouter(creators: creators);
  }

  /// Attempts to handle a request, according to its hostname.
  ///
  /// If none is matched, then `true` is returned.
  /// Also returns `true` if all of the sub-app's handlers returned
  /// `true`.
  Future<bool> handleRequest(RequestContext req, ResponseContext res) async {
    for (var pattern in _patterns) {
      // print('${req.hostname} vs $_creators');
      if (pattern.allMatches(req.hostname).isNotEmpty) {
        // Resolve the entire pipeline within the context of the selected app.
        var app = _apps[pattern] ??= (await _creators[pattern]!());
        // print('App for ${req.hostname} = $app from $pattern');
        // app.dumpTree();

        var r = app.optimizedRouter;
        var resolved = r.resolveAbsolute(req.path, method: req.method);
        var pipeline = MiddlewarePipeline<RequestHandler>(resolved);
        // print('Pipeline: $pipeline');
        for (var handler in pipeline.handlers) {
          // print(handler);
          // Avoid stack overflow.
          if (handler == handleRequest) {
            continue;
          } else if (!await app.executeHandler(handler, req, res)) {
            // print('$handler TERMINATED');
            return false;
          } else {
            // print('$handler CONTINUED');
          }
        }
      }
    }

    // Otherwise, return true.
    return true;
  }
}
