import 'dart:async';
import 'dart:convert';
import 'dart:ffi' as ffi;
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:ffi/ffi.dart';
import 'package:flut/main.dart' show flutRuntime;

// When Dart calls Python, used by invokeNativeSync
typedef _NativePythonFunction = ffi.Pointer<Utf8> Function(ffi.Pointer<Utf8>);

// When Python calls Dart, used for callbacks
typedef _NativePythonToDartFunction =
    ffi.Pointer<Utf8> Function(ffi.Pointer<Utf8>);

// When Python notifies Dart of set state
typedef _NativeSetStateFunction = ffi.Void Function(ffi.Pointer<Utf8>);

// DartApi DL table structs for walking NativeApi.initializeApiDLData
final class _DartApiEntry extends ffi.Struct {
  external ffi.Pointer<ffi.Char> name;
  external ffi.Pointer<ffi.Void> function;
}

final class _DartApi extends ffi.Struct {
  @ffi.Int32()
  external int major;
  @ffi.Int32()
  external int minor;
  external ffi.Pointer<_DartApiEntry> functions;
}

/// Interface for states that need to receive updates from Python
abstract class FlutState {
  void updateFromPython(Map<String, dynamic>? newSubtree);
}

abstract class FlutNative {
  Map<String, FlutState> get stateRegistry;

  dynamic invokeNativeSync(String type, Map<String, dynamic> data);
  void invokeNativeNoWaitSync(String type, Map<String, dynamic> data);
  Future<dynamic> invokeNativeAsync(String type, Map<String, dynamic> data);

  void dispose();
}

class FlutFfiNative implements FlutNative {
  static FlutFfiNative? _instance;

  final int nativeCallbackAddr;

  // Global registries
  @override
  final Map<String, FlutState> stateRegistry = {};

  // FFI Resources
  ffi.NativeCallable<_NativePythonToDartFunction>? _dartCallCallable;
  ffi.NativeCallable<_NativeSetStateFunction>? _setStateCallable;
  ffi.Pointer<Utf8>? _callResponseBuffer;

  FlutFfiNative(this.nativeCallbackAddr) {
    if (_instance != null) {
      throw Exception("FlutFfiNative already initialized");
    }
    _instance = this;
    _registerDartCallbackWithPython();
  }

  @override
  void dispose() {
    _instance = null;
    if (_callResponseBuffer != null) {
      calloc.free(_callResponseBuffer!);
      _callResponseBuffer = null;
    }
    _dartCallCallable?.close();
    _setStateCallable?.close();
  }

  // ===========================================================================
  // FFI Invocation
  // ===========================================================================

  @override
  dynamic invokeNativeSync(String type, Map<String, dynamic> data) {
    final req = jsonEncode({"type": type, "data": data});
    final reqPtr = req.toNativeUtf8();

    try {
      final ptr =
          ffi.Pointer<ffi.NativeFunction<_NativePythonFunction>>.fromAddress(
            nativeCallbackAddr,
          );
      final function = ptr.asFunction<_NativePythonFunction>();
      final resPtr = function(reqPtr);

      if (resPtr == ffi.nullptr) return null;

      final resJson = resPtr.toDartString();
      return jsonDecode(resJson);
    } finally {
      calloc.free(reqPtr);
    }
  }

  /// Invoke the native callback without synchronous dependency on result.
  /// Fire-and-forget: does NOT return a result.
  @override
  void invokeNativeNoWaitSync(String type, Map<String, dynamic> data) {
    Future<dynamic>(() {
      try {
        invokeNativeSync(type, data);
      } catch (e, st) {
        debugPrint('Error in invokeNativeNoWaitSync($type): $e');
        assert(() {
          debugPrint(st.toString());
          return true;
        }());
      }
    });
  }

  /// Invoke the native callback asynchronously and return the result.
  /// Dart UI doesn't block, but Python processes on main thread where call_dart works.
  @override
  Future<dynamic> invokeNativeAsync(String type, Map<String, dynamic> data) {
    return Future<dynamic>(() {
      try {
        return invokeNativeSync(type, data);
      } catch (e, st) {
        debugPrint('Error in invokeNativeAsync($type): $e');
        assert(() {
          debugPrint(st.toString());
          return true;
        }());
        return null;
      }
    });
  }

  // ===========================================================================
  // Callbacks
  // ===========================================================================

  void _registerDartCallbackWithPython() {
    _dartCallCallable =
        ffi.NativeCallable<_NativePythonToDartFunction>.isolateLocal(
          _handlePythonCallImpl,
        );

    final callCallbackAddr = _dartCallCallable!.nativeFunction.address;
    invokeNativeSync('register_dart_callback', {
      'callback_addr': callCallbackAddr,
    });

    _setStateCallable = ffi.NativeCallable<_NativeSetStateFunction>.listener(
      _onSetState,
    );

    final notifyCallbackAddr = _setStateCallable!.nativeFunction.address;
    invokeNativeSync('register_set_state_callback', {
      'callback_addr': notifyCallbackAddr,
    });

    _registerIsolateFunctions();
  }

  void _registerIsolateFunctions() {
    final apiData = ffi.NativeApi.initializeApiDLData.cast<_DartApi>();
    var entry = apiData.ref.functions;

    int? enterAddr;
    int? exitAddr;
    int? currentAddr;

    while (entry.ref.name != ffi.nullptr) {
      final name = entry.ref.name.cast<Utf8>().toDartString();
      if (name == 'Dart_EnterIsolate') {
        enterAddr = entry.ref.function.address;
      } else if (name == 'Dart_ExitIsolate') {
        exitAddr = entry.ref.function.address;
      } else if (name == 'Dart_CurrentIsolate') {
        currentAddr = entry.ref.function.address;
      }
      if (enterAddr != null && exitAddr != null && currentAddr != null) break;
      entry = ffi.Pointer.fromAddress(
        entry.address + ffi.sizeOf<_DartApiEntry>(),
      );
    }

    if (enterAddr == null || exitAddr == null || currentAddr == null) {
      debugPrint(
        '[Flut] Warning: Could not find isolate functions in DL table',
      );
      return;
    }

    final currentIsolateFn =
        ffi.Pointer<
              ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>
            >.fromAddress(currentAddr)
            .asFunction<ffi.Pointer<ffi.Void> Function()>();
    final isolateHandle = currentIsolateFn();

    invokeNativeSync('register_isolate_functions', {
      'enter': enterAddr,
      'exit': exitAddr,
      'current': currentAddr,
      'isolate': isolateHandle.address,
    });
  }

  static ffi.Pointer<Utf8> _handlePythonCallImpl(ffi.Pointer<Utf8> requestPtr) {
    if (_instance == null) {
      return '{"error": "No bridge active"}'.toNativeUtf8();
    }

    final instance = _instance!;

    try {
      final reqJson = requestPtr.toDartString();
      final request = jsonDecode(reqJson) as Map<String, dynamic>;
      final callType = request['type'] as String?;
      final data = request['data'] as Map<String, dynamic>? ?? {};

      Map<String, dynamic>? response;

      if (callType == 'get') {
        response = _getProperty(instance, data);
      } else if (callType == 'set') {
        response = _setProperty(instance, data);
      } else if (callType == 'call') {
        response = _callMethod(instance, data);
      } else if (callType == 'static') {
        response = _staticCall(instance, data);
      } else if (callType == 'release') {
        response = _releaseObjects(instance, data);
      } else if (callType == 'createObject') {
        final oid = flutRuntime.createObject(data);
        response = {'oid': oid};
      } else {
        response = {'error': 'Unknown call type', 'type': callType};
      }

      // Buffer management...
      if (instance._callResponseBuffer != null) {
        calloc.free(instance._callResponseBuffer!);
      }

      final responseJson = jsonEncode(response);
      instance._callResponseBuffer = responseJson.toNativeUtf8();
      return instance._callResponseBuffer!;
    } catch (e) {
      debugPrint('Error handling Python call: $e');
      instance._callResponseBuffer = '{"error": "exception"}'.toNativeUtf8();
      return instance._callResponseBuffer!;
    }
  }

  static void _onSetState(ffi.Pointer<Utf8> dataPtr) {
    if (_instance == null) return;

    try {
      final jsonStr = dataPtr.toDartString();
      if (jsonStr.isEmpty) return;

      final decoded = jsonDecode(jsonStr);
      if (decoded is String) {
        // setState notification (single state ID)
        final state = _instance!.stateRegistry[decoded];
        state?.updateFromPython(null);
      } else if (decoded is Map<String, dynamic>) {
        // Async dart call (fire-and-forget from Python)
        final type = decoded['_type'] as String?;
        if (type == 'set') {
          _setProperty(_instance!, decoded);
        } else if (type == 'call') {
          _callMethod(_instance!, decoded);
        }
      }
    } catch (e) {
      debugPrint('Error handling Python notify: $e');
    }
  }

  // ===========================================================================
  // Generic Property Access (by oid)
  // ===========================================================================

  static Map<String, dynamic> _getProperty(
    FlutFfiNative instance,
    Map<String, dynamic> data,
  ) {
    final oid = data['oid'] as int?;
    final property = data['property'] as String?;

    if (oid == null || property == null) {
      return {'error': 'Missing oid or property'};
    }

    final obj = flutRuntime.objectRegistry[oid];
    if (obj == null) {
      return {'error': 'Object not found', 'oid': oid};
    }

    final value = obj.getProperty(property);
    if (value != null) {
      return {'value': value};
    }
    return {'error': 'Unknown property', 'property': property};
  }

  static Map<String, dynamic> _setProperty(
    FlutFfiNative instance,
    Map<String, dynamic> data,
  ) {
    final oid = data['oid'] as int?;
    final property = data['property'] as String?;
    final value = data['value'];

    if (oid == null || property == null) {
      return {'error': 'Missing oid or property'};
    }

    final obj = flutRuntime.objectRegistry[oid];
    if (obj == null) {
      return {'error': 'Object not found', 'oid': oid};
    }

    if (obj.setProperty(property, value)) {
      return {'success': true};
    }
    return {'error': 'Unknown property', 'property': property};
  }

  static Map<String, dynamic> _callMethod(
    FlutFfiNative instance,
    Map<String, dynamic> data,
  ) {
    final oid = data['oid'] as int?;
    final method = data['method'] as String?;
    final args = data['args'] as Map<String, dynamic>? ?? {};

    if (oid == null || method == null) {
      return {'error': 'Missing oid or method'};
    }

    final obj = flutRuntime.objectRegistry[oid];
    if (obj == null) {
      return {'error': 'Object not found', 'oid': oid};
    }

    final result = obj.callMethod(method, args);
    if (result != null) {
      return {'value': result};
    }
    return {'error': 'Unknown method', 'method': method};
  }

  /// Handle static function calls (e.g., Theme.of, MediaQuery.of)
  /// Generic layer - routes to runtime for actual implementation
  static Map<String, dynamic> _staticCall(
    FlutFfiNative instance,
    Map<String, dynamic> data,
  ) {
    final name = data['name'] as String?;
    final args = data['args'] as List<dynamic>? ?? [];
    final kwargs = data['kwargs'] as Map<String, dynamic>?;

    if (name == null) {
      return {'error': 'Missing function name'};
    }

    return flutRuntime.handleStaticCall(name, args, kwargs);
  }

  static Map<String, dynamic> _releaseObjects(
    FlutFfiNative instance,
    Map<String, dynamic> data,
  ) {
    final oids = data['oids'] as List<dynamic>?;
    if (oids == null) {
      return {'error': 'Missing oids'};
    }

    int released = 0;
    for (final oid in oids) {
      if (oid is int) {
        final obj = flutRuntime.objectRegistry[oid];
        if (obj != null) {
          flutRuntime.releaseObject(oid);
          debugPrint(
            '[Flut GC] Released reference of oid=$oid type=${obj.runtimeType}',
          );
          released++;
        }
      }
    }
    if (released > 0) {
      debugPrint('[Flut GC] Total references released: $released references');
    }
    return {'released': released};
  }
}
