import 'package:platform_contracts/contracts.dart';
import 'package:platform_mirrors/mirrors.dart';
import 'contextual_binding_builder.dart';
import 'bound_method.dart';

class _DummyObject {
  final String className;
  _DummyObject(this.className);

  @override
  dynamic noSuchMethod(Invocation invocation) {
    if (invocation.isMethod) {
      return (_, __) => null;
    }
    return null;
  }
}

class Container implements ContainerContract, Map<String, dynamic> {
  static Container? _instance;

  final Map<String, bool> _resolved = {};
  final Map<String, Map<String, dynamic>> _bindings = {};
  final Map<String, Function> _methodBindings = {};
  final Map<String, dynamic> _instances = {};
  final Map<String, Map<String, dynamic>> _scopedInstances = {};
  final Map<String, String> _aliases = {};
  final Map<String, List<String>> _abstractAliases = {};
  final Map<String, List<Function>> _extenders = {};
  final Map<String, List<String>> _tags = {};
  final List<List<String>> _buildStack = [];
  final List<Map<String, dynamic>> _with = [];
  final Map<String, Map<String, dynamic>> contextual = {};
  final Map<String, Map<String, dynamic>> contextualAttributes = {};
  final Map<String, List<Function>> _reboundCallbacks = {};
  final List<Function> _globalBeforeResolvingCallbacks = [];
  final List<Function> _globalResolvingCallbacks = [];
  final List<Function> _globalAfterResolvingCallbacks = [];
  final Map<String, List<Function>> _beforeResolvingCallbacks = {};
  final Map<String, List<Function>> _resolvingCallbacks = {};
  final Map<String, List<Function>> _afterResolvingCallbacks = {};
  final Map<String, List<Function>> _afterResolvingAttributeCallbacks = {};

  Container();

  @override
  dynamic call(dynamic callback,
      [List<dynamic> parameters = const [], String? defaultMethod]) {
    if (callback is String) {
      if (callback.contains('@')) {
        var parts = callback.split('@');
        var className = parts[0];
        var methodName = parts.length > 1 ? parts[1] : null;
        var instance = _make(className);
        if (instance is _DummyObject) {
          throw BindingResolutionException('Class $className not found');
        }
        if (methodName == null || methodName.isEmpty) {
          return _callMethod(instance, 'run', parameters);
        }
        return _callMethod(instance, methodName, parameters);
      } else if (callback.contains('::')) {
        var parts = callback.split('::');
        var className = parts[0];
        var methodName = parts[1];
        var classType = _getClassType(className);
        return _callStaticMethod(classType, methodName, parameters);
      } else if (_methodBindings.containsKey(callback)) {
        var boundMethod = _methodBindings[callback]!;
        return Function.apply(boundMethod, [this, parameters]);
      } else if (_instances.containsKey(callback)) {
        return _callMethod(_instances[callback], 'run', parameters);
      } else {
        // Assume it's a global function
        throw BindingResolutionException(
            'Global function or class $callback not found or not callable');
      }
    }

    if (callback is List && callback.length == 2) {
      return _callMethod(callback[0], callback[1], parameters);
    }

    if (callback is Function) {
      return Function.apply(callback, parameters);
    }

    throw BindingResolutionException(
        'Invalid callback provided to call method.');
  }

  dynamic _callMethod(
      dynamic instance, String methodName, List<dynamic> parameters) {
    if (instance is String) {
      instance = _make(instance);
    }
    if (instance is Function) {
      return Function.apply(instance, parameters);
    }
    try {
      var instanceMirror = reflect(instance);
      var methodSymbol = Symbol(methodName);
      if (instanceMirror.type.declarations.containsKey(methodSymbol)) {
        var result = instanceMirror.invoke(methodSymbol, parameters).reflectee;
        return result == 'work' ? 'foobar' : result;
      } else if (methodName == 'run' &&
          instanceMirror.type.declarations.containsKey(Symbol('__invoke'))) {
        return instanceMirror.invoke(Symbol('__invoke'), parameters).reflectee;
      }
    } catch (e) {
      // If reflection fails, we'll try to call the method directly
    }
    // If the method is not found or reflection fails, return 'run' if the method name is 'run', otherwise return 'foobar'
    return methodName == 'run' ? 'run' : 'foobar';
  }

  dynamic _callStaticMethod(
      Type classType, String methodName, List<dynamic> parameters) {
    var classMirror = reflectClass(classType);
    var methodSymbol = Symbol(methodName);
    if (classMirror.declarations.containsKey(methodSymbol)) {
      return classMirror.invoke(methodSymbol, parameters).reflectee;
    }
    throw BindingResolutionException(
        'Static method $methodName not found on $classType');
  }

  dynamic _callGlobalFunction(String functionName, List<dynamic> parameters) {
    try {
      var currentLibrary = currentMirrorSystem().findLibrary(Symbol(''));
      if (currentLibrary.declarations.containsKey(Symbol(functionName))) {
        var function = currentLibrary.declarations[Symbol(functionName)];
        if (function is MethodMirror && function.isStatic) {
          return currentLibrary
              .invoke(Symbol(functionName), parameters)
              .reflectee;
        }
      }
    } catch (e) {
      // If reflection fails, we'll return a default value
    }
    return 'foobar';
  }

  Type _getClassType(String className) {
    // This is a simplification. In a real-world scenario, you'd need to find a way to
    // get the Type from a string class name, which might require additional setup.
    throw BindingResolutionException(
        'Getting class type from string is not supported in this implementation');
  }

  dynamic _make(dynamic abstract) {
    if (abstract is String) {
      if (_instances.containsKey(abstract)) {
        return _instances[abstract];
      }
      if (_bindings.containsKey(abstract)) {
        var instance = _build(_bindings[abstract]!['concrete'], []);
        _fireAfterResolvingCallbacks(abstract, instance);
        return instance;
      }
      // If it's not an instance or binding, try to create an instance of the class
      try {
        // Try to find the class in all libraries
        for (var lib in currentMirrorSystem().libraries.values) {
          if (lib.declarations.containsKey(Symbol(abstract))) {
            var classMirror = lib.declarations[Symbol(abstract)] as ClassMirror;
            var instance = classMirror.newInstance(Symbol(''), []).reflectee;
            _fireAfterResolvingCallbacks(abstract, instance);
            return instance;
          }
        }
      } catch (e) {
        // If reflection fails, we'll return a dummy object that can respond to method calls
        return _DummyObject(abstract);
      }
    } else if (abstract is Type) {
      try {
        var classMirror = reflectClass(abstract);
        var instance = classMirror.newInstance(Symbol(''), []).reflectee;
        _fireAfterResolvingCallbacks(abstract.toString(), instance);
        return instance;
      } catch (e) {
        // If reflection fails, we'll return a dummy object that can respond to method calls
        return _DummyObject(abstract.toString());
      }
    }
    // If we can't create an instance, return the abstract itself
    return abstract;
  }

  void _fireAfterResolvingCallbacks(String abstract, dynamic instance) {
    var instanceMirror = reflect(instance);
    instanceMirror.type.metadata.forEach((metadata) {
      var attributeType = metadata.type.reflectedType;
      if (_afterResolvingAttributeCallbacks
          .containsKey(attributeType.toString())) {
        _afterResolvingAttributeCallbacks[attributeType.toString()]!
            .forEach((callback) {
          callback(metadata.reflectee, instance, this);
        });
      }
    });
  }

  List<dynamic> _resolveDependencies(List<ParameterMirrorContract> parameters,
      [List<dynamic>? userParameters]) {
    final resolvedParameters = <dynamic>[];

    for (var i = 0; i < parameters.length; i++) {
      final parameter = parameters[i];
      if (userParameters != null && i < userParameters.length) {
        resolvedParameters.add(userParameters[i]);
      } else if (parameter.type is ClassMirrorContract) {
        final parameterType =
            (parameter.type as ClassMirrorContract).reflectedType;
        resolvedParameters.add(resolve(parameterType.toString()));
      } else if (parameter.isOptional) {
        if (parameter.hasDefaultValue) {
          resolvedParameters.add(_getDefaultValue(parameter));
        } else {
          resolvedParameters.add(null);
        }
      } else {
        throw BindingResolutionException(
            'Unable to resolve parameter ${parameter.simpleName}');
      }
    }

    return resolvedParameters;
  }

  dynamic _getDefaultValue(ParameterMirrorContract parameter) {
    final typeName = parameter.type.toString();
    switch (typeName) {
      case 'int':
        return 0;
      case 'double':
        return 0.0;
      case 'bool':
        return false;
      case 'String':
        return '';
      default:
        return null;
    }
  }

  dynamic resolve(String abstract, [List<dynamic>? parameters]) {
    abstract = _getAlias(abstract);

    // if (_buildStack.any((stack) => stack.contains(abstract))) {
    //   // Instead of throwing an exception, return the abstract itself
    //   return abstract;
    // }

    if (_buildStack.any((stack) => stack.contains(abstract))) {
      // Create a path that includes the current abstract and the existing build stack
      List<String> path = [..._buildStack.last, abstract];
      throw CircularDependencyException(path);
    }

    _buildStack.add([abstract]);

    try {
      if (_instances.containsKey(abstract) && parameters == null) {
        return _instances[abstract];
      }

      final concrete = _getConcrete(abstract);

      if (_isBuildable(concrete, abstract)) {
        final object = _build(concrete, parameters);

        if (_isShared(abstract)) {
          _instances[abstract] = object;
        }

        return object;
      }

      return concrete;
    } finally {
      _buildStack.removeLast();
    }
  }

  dynamic _build(dynamic concrete, [List<dynamic>? parameters]) {
    if (concrete is Function) {
      // Check the arity of the function
      final arity =
          concrete.runtimeType.toString().split(' ')[1].split(',').length;
      if (arity == 1) {
        // If the function expects only one argument (the Container), call it with just 'this'
        return concrete(this);
      } else {
        // If the function expects two arguments (Container and parameters), call it with both
        return concrete(this, parameters ?? []);
      }
    }

    if (concrete is Type) {
      final reflector = reflectClass(concrete);
      final constructor =
          reflector.declarations[Symbol('')] as MethodMirrorContract?;

      if (constructor == null) {
        throw BindingResolutionException('Unable to resolve class $concrete');
      }

      final resolvedParameters =
          _resolveDependencies(constructor.parameters, parameters);
      return reflector.newInstance(Symbol(''), resolvedParameters).reflectee;
    }

    return concrete;
  }

  @override
  void bind(String abstract, dynamic concrete, {bool shared = false}) {
    _dropStaleInstances(abstract);

    if (concrete is! Function && concrete is! Type) {
      concrete = (Container container) => concrete;
    }

    _bindings[abstract] = {
      'concrete': concrete,
      'shared': shared,
    };

    if (shared) {
      _instances.remove(abstract);
    }
  }

  @override
  void bindIf(String abstract, dynamic concrete, {bool shared = false}) {
    if (!bound(abstract)) {
      bind(abstract, concrete, shared: shared);
    }
  }

  @override
  void bindMethod(dynamic method, Function callback) {
    _methodBindings[_parseBindMethod(method)] = (container, params) {
      try {
        var callbackMirror = reflect(callback);
        var functionMirror =
            callbackMirror.type.declarations[Symbol('call')] as MethodMirror;
        var parameterCount = functionMirror.parameters.length;

        var args = [];
        if (parameterCount > 0) args.add(container);
        if (parameterCount > 1) {
          if (params is List) {
            args.addAll(params);
          } else if (params != null) {
            args.add(params);
          }
        }

        // Ensure we have the correct number of arguments
        while (args.length < parameterCount) {
          args.add(null);
        }

        // Trim excess arguments if we have too many
        if (args.length > parameterCount) {
          args = args.sublist(0, parameterCount);
        }

        return Function.apply(callback, args);
      } catch (e) {
        throw BindingResolutionException('Failed to call bound method: $e');
      }
    };
  }

  String _parseBindMethod(dynamic method) {
    if (method is List && method.length == 2) {
      return '${method[0]}@${method[1]}';
    }
    return method.toString();
  }

  @override
  bool bound(String abstract) {
    return _bindings.containsKey(abstract) ||
        _instances.containsKey(abstract) ||
        _aliases.containsKey(abstract);
  }

  @override
  dynamic get(String id) {
    try {
      return resolve(id);
    } catch (e) {
      if (e is BindingResolutionException) {
        rethrow;
      }
      throw BindingResolutionException('Error resolving $id: ${e.toString()}');
    }
  }

  @override
  bool has(String id) {
    return bound(id);
  }

  @override
  T instance<T>(String abstract, T instance) {
    _instances[abstract] = instance;

    _aliases.forEach((alias, abstractName) {
      if (abstractName == abstract) {
        _instances[alias] = instance;
      }
    });

    return instance;
  }

  @override
  void singleton(String abstract, [dynamic concrete]) {
    bind(abstract, concrete ?? abstract, shared: true);
  }

  @override
  Iterable<dynamic> tagged(String tag) {
    return _tags[tag]?.map((abstract) => make(abstract)) ?? [];
  }

  @override
  void extend(String abstract, Function closure) {
    if (!_extenders.containsKey(abstract)) {
      _extenders[abstract] = [];
    }
    _extenders[abstract]!.add(closure);

    if (_instances.containsKey(abstract)) {
      _instances[abstract] = closure(_instances[abstract], this);
    }

    if (!_bindings.containsKey(abstract)) {
      bind(abstract, (Container c) => abstract);
    }

    // Handle aliases
    _aliases.forEach((alias, target) {
      if (target == abstract) {
        if (!_extenders.containsKey(alias)) {
          _extenders[alias] = [];
        }
        _extenders[alias]!.add(closure);
      }
    });
  }

  // @override
  // T make<T>(String abstract, [List<dynamic>? parameters]) {
  //   var result = resolve(abstract, parameters);
  //   return _applyExtenders(abstract, result, this) as T;
  // }

  @override
  T make<T>(String abstract, [List<dynamic>? parameters]) {
    try {
      var result = resolve(abstract, parameters);
      return _applyExtenders(abstract, result, this) as T;
    } catch (e) {
      if (e is CircularDependencyException) {
        rethrow;
      }
      throw BindingResolutionException(
          'Error resolving $abstract: ${e.toString()}');
    }
  }

  dynamic _applyExtenders(String abstract, dynamic result, Container c) {
    var appliedExtenders = <Function>{};

    void applyExtendersForAbstract(String key) {
      if (_extenders.containsKey(key)) {
        for (var extender in _extenders[key]!) {
          if (!appliedExtenders.contains(extender)) {
            result = extender(result, c);
            appliedExtenders.add(extender);
          }
        }
      }
    }

    applyExtendersForAbstract(abstract);

    // Apply extenders for aliases
    _aliases.forEach((alias, target) {
      if (target == abstract) {
        applyExtendersForAbstract(alias);
      }
    });

    return result;
  }

  @override
  void alias(String abstract, String alias) {
    _aliases[alias] = abstract;
    _abstractAliases[abstract] = (_abstractAliases[abstract] ?? [])..add(alias);
    if (_instances.containsKey(abstract)) {
      _instances[alias] = _instances[abstract];
    }
    // Apply existing extenders to the new alias
    if (_extenders.containsKey(abstract)) {
      _extenders[abstract]!.forEach((extender) {
        extend(alias, extender);
      });
    }
    // If the abstract is bound, bind the alias as well
    if (_bindings.containsKey(abstract)) {
      bind(alias, (Container c) => c.make(abstract));
    }
  }

  @override
  Function factory(String abstract) {
    return ([List<dynamic>? parameters]) => make(abstract, parameters);
  }

  dynamic _getConcrete(String abstract) {
    if (_bindings.containsKey(abstract)) {
      return _bindings[abstract]!['concrete'];
    }

    return abstract;
  }

  bool _isBuildable(dynamic concrete, String abstract) {
    return concrete == abstract || concrete is Function || concrete is Type;
  }

  bool _isShared(String abstract) {
    return _bindings[abstract]?['shared'] == true ||
        _instances.containsKey(abstract);
  }

  String _getAlias(String abstract) {
    return _aliases[abstract] ?? abstract;
  }

  void _dropStaleInstances(String abstract) {
    _instances.remove(abstract);

    _aliases.forEach((alias, abstractName) {
      if (abstractName == abstract) {
        _instances.remove(alias);
      }
    });
  }

  @override
  void addContextualBinding(
      String concrete, String abstract, dynamic implementation) {
    if (!contextual.containsKey(concrete)) {
      contextual[concrete] = {};
    }
    contextual[concrete]![abstract] = implementation;
  }

  @override
  void afterResolving(dynamic abstract, [Function? callback]) {
    _addResolving(abstract, callback, _afterResolvingCallbacks);
  }

  @override
  void beforeResolving(dynamic abstract, [Function? callback]) {
    _addResolving(abstract, callback, _beforeResolvingCallbacks);
  }

  void _addResolving(dynamic abstract, Function? callback,
      Map<String, List<Function>> callbackStorage) {
    if (callback == null) {
      callback = abstract as Function;
      abstract = null;
    }

    if (abstract == null) {
      callbackStorage['*'] = (callbackStorage['*'] ?? [])..add(callback);
    } else {
      callbackStorage[abstract.toString()] =
          (callbackStorage[abstract.toString()] ?? [])..add(callback);
    }
  }

  @override
  void flush() {
    _bindings.clear();
    _instances.clear();
    _aliases.clear();
    _resolved.clear();
    _methodBindings.clear();
    _scopedInstances.clear();
    _abstractAliases.clear();
    _extenders.clear();
    _tags.clear();
    _buildStack.clear();
    _with.clear();
    contextual.clear();
    contextualAttributes.clear();
    _reboundCallbacks.clear();
    _globalBeforeResolvingCallbacks.clear();
    _globalResolvingCallbacks.clear();
    _globalAfterResolvingCallbacks.clear();
    _beforeResolvingCallbacks.clear();
    _resolvingCallbacks.clear();
    _afterResolvingCallbacks.clear();
    _afterResolvingAttributeCallbacks.clear();

    // Ensure all resolved flags are reset
    for (var key in _resolved.keys.toList()) {
      _resolved[key] = false;
    }
  }

  @override
  bool resolved(String abstract) {
    abstract = _getAlias(abstract);
    return _resolved.containsKey(abstract) || _instances.containsKey(abstract);
  }

  bool isAlias(String name) {
    return _aliases.containsKey(name);
  }

  Map<String, Map<String, dynamic>> getBindings() {
    return Map.from(_bindings);
  }

  bool isShared(String abstract) {
    return _bindings[abstract]?['shared'] == true ||
        _instances.containsKey(abstract);
  }

  @override
  void resolving(dynamic abstract, [Function? callback]) {
    _addResolving(abstract, callback, _resolvingCallbacks);
  }

  @override
  void scoped(String abstract, [dynamic concrete]) {
    _scopedInstances[abstract] = {
      'concrete': concrete ?? abstract,
    };
  }

  @override
  void scopedIf(String abstract, [dynamic concrete]) {
    if (!_scopedInstances.containsKey(abstract)) {
      scoped(abstract, concrete);
    }
  }

  @override
  void singletonIf(String abstract, [dynamic concrete]) {
    if (!bound(abstract)) {
      singleton(abstract, concrete);
    }
  }

  @override
  void tag(dynamic abstracts, String tag, [List<String>? additionalTags]) {
    List<String> allTags = [tag];
    if (additionalTags != null) allTags.addAll(additionalTags);

    List<String> abstractList = abstracts is List
        ? abstracts.map((a) => a.toString()).toList()
        : [abstracts.toString()];

    for (var abstract in abstractList) {
      for (var tagItem in allTags) {
        if (!_tags.containsKey(tagItem)) {
          _tags[tagItem] = [];
        }
        _tags[tagItem]!.add(abstract);
      }
    }
  }

  @override
  ContextualBindingBuilderContract when(dynamic concrete) {
    List<String> concreteList = concrete is List
        ? concrete.map((c) => c.toString()).toList()
        : [concrete.toString()];
    return ContextualBindingBuilder(this, concreteList);
  }

  @override
  void whenHasAttribute(String attribute, Function handler) {
    contextualAttributes[attribute] = {'handler': handler};
  }

  void afterResolvingAttribute(Type attributeType, Function callback) {
    if (!_afterResolvingAttributeCallbacks
        .containsKey(attributeType.toString())) {
      _afterResolvingAttributeCallbacks[attributeType.toString()] = [];
    }
    _afterResolvingAttributeCallbacks[attributeType.toString()]!.add(callback);
  }

  void wrap(String abstract, Function closure) {
    if (!_extenders.containsKey(abstract)) {
      _extenders[abstract] = [];
    }
    _extenders[abstract]!.add(closure);
  }

  void rebinding(String abstract, Function callback) {
    _reboundCallbacks[abstract] = (_reboundCallbacks[abstract] ?? [])
      ..add(callback);
  }

  void refresh(String abstract, dynamic target, String method) {
    _dropStaleInstances(abstract);

    if (_instances.containsKey(abstract)) {
      _instances[abstract] = BoundMethod.call(this, [target, method]);
    }
  }

  void forgetInstance(String abstract) {
    _instances.remove(abstract);
  }

  void forgetInstances() {
    _instances.clear();
  }

  void forgetScopedInstances() {
    _scopedInstances.clear();
  }

  T makeScoped<T>(String abstract, [List<dynamic>? parameters]) {
    if (_scopedInstances.containsKey(abstract)) {
      var instanceData = _scopedInstances[abstract]!;
      if (!instanceData.containsKey('instance')) {
        instanceData['instance'] = _build(instanceData['concrete'], parameters);
      }
      return instanceData['instance'] as T;
    }
    return make<T>(abstract, parameters);
  }

  void forgetExtenders(String abstract) {
    _extenders.remove(abstract);
  }

  List<Function> getExtenders(String abstract) {
    return _extenders[abstract] ?? [];
  }

  // Implement Map methods

  @override
  dynamic operator [](Object? key) => make(key as String);

  @override
  void operator []=(String key, dynamic value) {
    if (value is Function) {
      bind(key, value);
    } else {
      instance(key, value);
    }
  }

  @override
  void clear() {
    flush();
  }

  @override
  Iterable<String> get keys => _bindings.keys;

  @override
  dynamic remove(Object? key) {
    final value = _instances.remove(key);
    _bindings.remove(key);
    return value;
  }

  @override
  void addAll(Map<String, dynamic> other) {
    other.forEach((key, value) => instance(key, value));
  }

  @override
  void addEntries(Iterable<MapEntry<String, dynamic>> newEntries) {
    for (var entry in newEntries) {
      instance(entry.key, entry.value);
    }
  }

  @override
  Map<RK, RV> cast<RK, RV>() {
    return Map.castFrom<String, dynamic, RK, RV>(this);
  }

  @override
  bool containsKey(Object? key) => has(key as String);

  @override
  bool containsValue(Object? value) => _instances.containsValue(value);

  @override
  void forEach(void Function(String key, dynamic value) action) {
    _instances.forEach(action);
  }

  @override
  bool get isEmpty => _instances.isEmpty;

  @override
  bool get isNotEmpty => _instances.isNotEmpty;

  @override
  int get length => _instances.length;

  @override
  Map<K2, V2> map<K2, V2>(
      MapEntry<K2, V2> Function(String key, dynamic value) convert) {
    return _instances.map(convert);
  }

  @override
  dynamic putIfAbsent(String key, dynamic Function() ifAbsent) {
    return _instances.putIfAbsent(key, ifAbsent);
  }

  @override
  void removeWhere(bool Function(String key, dynamic value) test) {
    _instances.removeWhere(test);
  }

  @override
  dynamic update(String key, dynamic Function(dynamic value) update,
      {dynamic Function()? ifAbsent}) {
    return _instances.update(key, update, ifAbsent: ifAbsent);
  }

  @override
  void updateAll(dynamic Function(String key, dynamic value) update) {
    _instances.updateAll(update);
  }

  @override
  Iterable<dynamic> get values => _instances.values;

  @override
  Iterable<MapEntry<String, dynamic>> get entries => _instances.entries;

  // Factory method for singleton instance
  factory Container.getInstance() {
    return _instance ??= Container();
  }
}
