import 'dart:convert';
import 'dart:io';

import 'package:flutter/services.dart';
import 'package:tsnet_flutter/tsnet_ffi.dart';

/// Embedded Tailscale tsnet client for Flutter.
///
/// Provides a userspace WireGuard tunnel via Tailscale's tsnet library,
/// without requiring the user to install the Tailscale app or a VPN entitlement.
///
/// - iOS/macOS: Swift MethodChannel → C bridge → Go static library
/// - Android/Linux: Dart FFI → Go shared library (.so)
///
/// Usage:
/// ```dart
/// final ts = TsnetFlutter();
/// await ts.start(authKey: 'tskey-auth-...');
/// final port = await ts.startProxy('100.64.0.5');
/// // Connect your client to localhost:port — traffic tunnels via WireGuard
/// ```
class TsnetFlutter {
  static const _channel = MethodChannel('tsnet_flutter');

  /// FFI backend for Android/Linux — initialized lazily.
  TsnetFfi? _ffi;

  bool get _useFfi => Platform.isAndroid || Platform.isLinux;

  TsnetFfi get _ensureFfi => _ffi ??= TsnetFfi();

  /// State directory for persisting Tailscale identity.
  String? _stateDir;

  Future<String> _getStateDir() async {
    if (_stateDir != null) return _stateDir!;

    // Use a directory relative to the current working directory or temp
    // For proper production use, pass a path from path_provider
    late final Directory dir;
    if (Platform.isAndroid) {
      // Android: use the app's internal storage via the temp directory parent
      final temp = Directory.systemTemp;
      dir = Directory('${temp.parent.path}/tailscale_state');
    } else if (Platform.isLinux) {
      final home = Platform.environment['HOME'] ?? '/tmp';
      dir = Directory('$home/.local/share/tailscale_state');
    } else {
      dir = Directory('${Directory.systemTemp.path}/tailscale_state');
    }
    await dir.create(recursive: true);
    _stateDir = dir.path;
    return _stateDir!;
  }

  /// Join the Tailnet with the given auth key.
  Future<void> start({
    required String authKey,
    String hostname = 'tsnet-flutter',
  }) async {
    if (_useFfi) {
      final dir = await _getStateDir();
      await _ensureFfi.start(dir, authKey, hostname);
    } else {
      await _channel.invokeMethod('start', {
        'authKey': authKey,
        'hostname': hostname,
      });
    }
  }

  /// Create a localhost TCP proxy to a remote Tailscale IP.
  /// Returns the local port number.
  Future<int> startProxy(String remoteIP, {int port = 5050}) async {
    if (_useFfi) {
      return await _ensureFfi.startProxy(remoteIP, port);
    } else {
      final localPort = await _channel.invokeMethod<int>('startProxy', {
        'ip': remoteIP,
        'port': port,
      });
      return localPort!;
    }
  }

  /// Stop the local TCP proxy.
  Future<void> stopProxy() async {
    if (_useFfi) {
      await _ensureFfi.stopProxy();
    } else {
      await _channel.invokeMethod('stopProxy');
    }
  }

  /// Disconnect from the Tailnet and clean up.
  Future<void> stop() async {
    if (_useFfi) {
      await _ensureFfi.stop();
    } else {
      await _channel.invokeMethod('stop');
    }
  }

  /// Get the current Tailscale connection status.
  Future<TailscaleStatus> status() async {
    String? json;
    if (_useFfi) {
      json = await _ensureFfi.status();
    } else {
      json = await _channel.invokeMethod<String>('status');
    }
    if (json == null) {
      return TailscaleStatus(state: TailscaleState.stopped);
    }
    return TailscaleStatus.fromJson(jsonDecode(json) as Map<String, dynamic>);
  }

  /// Get this device's Tailscale IPv4 address (100.x.x.x).
  Future<String> tailscaleIP() async {
    if (_useFfi) {
      return await _ensureFfi.tailscaleIP();
    } else {
      final ip = await _channel.invokeMethod<String>('tailscaleIP');
      return ip!;
    }
  }
}

enum TailscaleState {
  stopped,
  starting,
  running,
  needsLogin,
}

class TailscaleStatus {
  final TailscaleState state;
  final String? selfIP;
  final String? backendState;
  final List<TailscalePeer> peers;

  TailscaleStatus({
    required this.state,
    this.selfIP,
    this.backendState,
    this.peers = const [],
  });

  factory TailscaleStatus.fromJson(Map<String, dynamic> json) {
    final backendState = json['BackendState'] as String? ?? '';
    final state = switch (backendState) {
      'Running' => TailscaleState.running,
      'Starting' => TailscaleState.starting,
      'NeedsLogin' || 'NeedsMachineAuth' => TailscaleState.needsLogin,
      _ => TailscaleState.stopped,
    };

    final selfNode = json['Self'] as Map<String, dynamic>?;
    final selfIP = selfNode?['TailscaleIPs'] is List
        ? (selfNode!['TailscaleIPs'] as List).firstOrNull as String?
        : null;

    final peerMap = json['Peer'] as Map<String, dynamic>? ?? {};
    final peers = peerMap.values
        .map((p) => TailscalePeer.fromJson(p as Map<String, dynamic>))
        .toList();

    return TailscaleStatus(
      state: state,
      selfIP: selfIP,
      backendState: backendState,
      peers: peers,
    );
  }

  bool get isRunning => state == TailscaleState.running;
}

class TailscalePeer {
  final String hostname;
  final List<String> tailscaleIPs;
  final bool online;

  TailscalePeer({
    required this.hostname,
    required this.tailscaleIPs,
    required this.online,
  });

  factory TailscalePeer.fromJson(Map<String, dynamic> json) {
    final ips = json['TailscaleIPs'] as List? ?? [];
    return TailscalePeer(
      hostname: json['HostName'] as String? ?? '',
      tailscaleIPs: ips.map((ip) => ip as String).toList(),
      online: json['Online'] as bool? ?? false,
    );
  }
}
