import 'dart:ffi';
import 'dart:io';

import 'package:ffi/ffi.dart';
import 'package:flutter/services.dart';

// C function typedefs from bridge.go
typedef _StartNative = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>);
typedef _StartProxyNative = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Int32>);
typedef _StartProxyDart = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Int32>);
typedef _SimpleNative = Pointer<Utf8> Function();
typedef _OutParamNative = Pointer<Utf8> Function(Pointer<Pointer<Utf8>>);
typedef _FreeNative = Void Function(Pointer<Utf8>);
typedef _FreeDart = void Function(Pointer<Utf8>);

/// FFI-based implementation for Android and Linux.
/// Loads the Go shared library and calls C functions directly.
class TsnetFfi {
  late final DynamicLibrary _lib;
  late final _StartNative _start;
  late final _StartProxyDart _startProxy;
  late final _SimpleNative _stopProxy;
  late final _SimpleNative _stop;
  late final _OutParamNative _status;
  late final _OutParamNative _tailscaleIP;
  late final _FreeDart _free;

  TsnetFfi() {
    _lib = _loadLibrary();
    _start = _lib.lookupFunction<_StartNative, _StartNative>('TailscaleStart');
    _startProxy = _lib.lookupFunction<_StartProxyNative, _StartProxyDart>('TailscaleStartProxy');
    _stopProxy = _lib.lookupFunction<_SimpleNative, _SimpleNative>('TailscaleStopProxy');
    _stop = _lib.lookupFunction<_SimpleNative, _SimpleNative>('TailscaleStop');
    _status = _lib.lookupFunction<_OutParamNative, _OutParamNative>('TailscaleStatus');
    _tailscaleIP = _lib.lookupFunction<_OutParamNative, _OutParamNative>('TailscaleIP');
    _free = _lib.lookupFunction<_FreeNative, _FreeDart>('TailscaleFreeString');
  }

  static DynamicLibrary _loadLibrary() {
    if (Platform.isAndroid) {
      return DynamicLibrary.open('libtailscale.so');
    } else if (Platform.isLinux) {
      // Look in the app's lib directory (where Flutter bundles native libs)
      return DynamicLibrary.open('libtailscale.so');
    }
    throw UnsupportedError('FFI not supported on ${Platform.operatingSystem}');
  }

  String? _consumeString(Pointer<Utf8> ptr) {
    if (ptr == nullptr) return null;
    final str = ptr.toDartString();
    _free(ptr);
    return str;
  }

  void _checkError(Pointer<Utf8> errPtr, String code) {
    final err = _consumeString(errPtr);
    if (err != null) {
      throw PlatformException(code: code, message: err);
    }
  }

  Future<void> start(String stateDir, String authKey, String hostname) async {
    final dirC = stateDir.toNativeUtf8();
    final keyC = authKey.toNativeUtf8();
    final hostC = hostname.toNativeUtf8();
    try {
      final err = _start(dirC, keyC, hostC);
      _checkError(err, 'START_FAILED');
    } finally {
      malloc.free(dirC);
      malloc.free(keyC);
      malloc.free(hostC);
    }
  }

  Future<int> startProxy(String remoteIP, int remotePort) async {
    final ipC = remoteIP.toNativeUtf8();
    final portOut = malloc<Int32>();
    try {
      final err = _startProxy(ipC, remotePort, portOut);
      _checkError(err, 'PROXY_FAILED');
      return portOut.value;
    } finally {
      malloc.free(ipC);
      malloc.free(portOut);
    }
  }

  Future<void> stopProxy() async {
    final err = _stopProxy();
    _checkError(err, 'STOP_PROXY_FAILED');
  }

  Future<void> stop() async {
    final err = _stop();
    _checkError(err, 'STOP_FAILED');
  }

  Future<String?> status() async {
    final errOut = malloc<Pointer<Utf8>>();
    try {
      errOut.value = nullptr;
      final json = _status(errOut);
      final err = _consumeString(errOut.value);
      if (err != null) throw PlatformException(code: 'STATUS_FAILED', message: err);
      return _consumeString(json);
    } finally {
      malloc.free(errOut);
    }
  }

  Future<String> tailscaleIP() async {
    final errOut = malloc<Pointer<Utf8>>();
    try {
      errOut.value = nullptr;
      final ip = _tailscaleIP(errOut);
      final err = _consumeString(errOut.value);
      if (err != null) throw PlatformException(code: 'IP_FAILED', message: err);
      return _consumeString(ip) ?? '';
    } finally {
      malloc.free(errOut);
    }
  }
}
