import 'dart:async';
import 'dart:mirrors';
import 'package:angel_framework/angel_framework.dart';
import 'plural.dart' as pluralize;
import 'no_service.dart';

HookedServiceEventListener hasManyThrough(String servicePath, String pivotPath,
    {String as,
    String localKey,
    String pivotKey,
    String foreignKey,
    getLocalKey(obj),
    getPivotKey(obj),
    getForeignKey(obj),
    assignForeignObjects(foreign, obj)}) {
  var foreignName =
      as?.isNotEmpty == true ? as : pluralize.plural(servicePath.toString());

  return (HookedServiceEvent e) async {
    var pivotService = e.getService(pivotPath);
    var foreignService = e.getService(servicePath);

    if (pivotService == null)
      throw noService(pivotPath);
    else if (foreignService == null) throw noService(servicePath);

    _assignForeignObjects(foreign, obj) {
      if (assignForeignObjects != null)
        return assignForeignObjects(foreign, obj);
      else if (obj is Map)
        obj[foreignName] = foreign;
      //TODO: Undefined class
      //else if (obj is Extensible)
      //  obj.properties[foreignName] = foreign;
      else
        reflect(obj).setField(new Symbol(foreignName), foreign);
    }

    _getLocalKey(obj) {
      if (getLocalKey != null)
        return getLocalKey(obj);
      else if (obj is Map)
        return obj[localKey ?? 'id'];
      //TODO: Undefined class
      //else if (obj is Extensible)
      //  return obj.properties[localKey ?? 'id'];
      else if (localKey == null || localKey == 'id')
        return obj.id;
      else
        return reflect(obj).getField(new Symbol(localKey ?? 'id')).reflectee;
    }

    _getPivotKey(obj) {
      if (getPivotKey != null)
        return getPivotKey(obj);
      else if (obj is Map)
        return obj[pivotKey ?? 'id'];
      //TODO: Undefined class
      //else if (obj is Extensible)
      //  return obj.properties[pivotKey ?? 'id'];
      else if (pivotKey == null || pivotKey == 'id')
        return obj.id;
      else
        return reflect(obj).getField(new Symbol(pivotKey ?? 'id')).reflectee;
    }

    _normalize(obj) async {
      // First, resolve pivot
      var id = await _getLocalKey(obj);
      var indexed = await pivotService.index({
        'query': {pivotKey ?? 'userId': id}
      });

      if (indexed == null || indexed is! List || indexed.isNotEmpty != true) {
        await _assignForeignObjects([], obj);
      } else {
        // Now, resolve from foreign service
        var mapped = await Future.wait(indexed.map((pivot) async {
          var id = await _getPivotKey(obj);
          var indexed = await foreignService.index({
            'query': {foreignKey ?? 'postId': id}
          });

          if (indexed == null ||
              indexed is! List ||
              indexed.isNotEmpty != true) {
            await _assignForeignObjects([], pivot);
          } else {
            await _assignForeignObjects(indexed, pivot);
          }

          return pivot;
        }));
        await _assignForeignObjects(mapped, obj);
      }
    }

    if (e.result is Iterable) {
      await Future.wait(e.result.map(_normalize));
    } else
      await _normalize(e.result);
  };
}
