/// Network connectivity information and monitoring
///
/// Provides real-time network status checking, connection type detection,
/// and connectivity monitoring for the Worker app.
library;

import 'dart:async';

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'network_info.g.dart';

// ============================================================================
// Network Connection Types
// ============================================================================

/// Types of network connections
enum NetworkConnectionType {
  /// WiFi connection
  wifi,

  /// Mobile data connection
  mobile,

  /// Ethernet connection (wired)
  ethernet,

  /// Bluetooth connection
  bluetooth,

  /// VPN connection
  vpn,

  /// No connection
  none,

  /// Unknown connection type
  unknown,
}

// ============================================================================
// Network Status
// ============================================================================

/// Network connectivity status
class NetworkStatus {
  const NetworkStatus({
    required this.isConnected,
    required this.connectionType,
    required this.timestamp,
  });

  factory NetworkStatus.connected(NetworkConnectionType type) {
    return NetworkStatus(
      isConnected: true,
      connectionType: type,
      timestamp: DateTime.now(),
    );
  }

  factory NetworkStatus.disconnected() {
    return NetworkStatus(
      isConnected: false,
      connectionType: NetworkConnectionType.none,
      timestamp: DateTime.now(),
    );
  }

  final bool isConnected;
  final NetworkConnectionType connectionType;
  final DateTime timestamp;

  /// Check if connected via WiFi
  bool get isWiFi => connectionType == NetworkConnectionType.wifi;

  /// Check if connected via mobile data
  bool get isMobile => connectionType == NetworkConnectionType.mobile;

  /// Check if connected via ethernet
  bool get isEthernet => connectionType == NetworkConnectionType.ethernet;

  /// Check if connection is metered (mobile data)
  bool get isMetered => isMobile;

  @override
  String toString() {
    return 'NetworkStatus(isConnected: $isConnected, type: $connectionType)';
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    return other is NetworkStatus &&
        other.isConnected == isConnected &&
        other.connectionType == connectionType;
  }

  @override
  int get hashCode => Object.hash(isConnected, connectionType);
}

// ============================================================================
// Network Info Interface
// ============================================================================

/// Abstract interface for network information
abstract class NetworkInfo {
  /// Check if device is currently connected to internet
  Future<bool> get isConnected;

  /// Get current network connection type
  Future<NetworkConnectionType> get connectionType;

  /// Get current network status
  Future<NetworkStatus> get networkStatus;

  /// Stream of network status changes
  Stream<NetworkStatus> get onNetworkStatusChanged;
}

// ============================================================================
// Network Info Implementation
// ============================================================================

/// Implementation of NetworkInfo using connectivity_plus
class NetworkInfoImpl implements NetworkInfo {
  NetworkInfoImpl(this._connectivity);

  final Connectivity _connectivity;
  StreamController<NetworkStatus>? _statusController;
  StreamSubscription<List<ConnectivityResult>>? _subscription;

  @override
  Future<bool> get isConnected async {
    final results = await _connectivity.checkConnectivity();
    return _hasConnection(results);
  }

  @override
  Future<NetworkConnectionType> get connectionType async {
    final results = await _connectivity.checkConnectivity();
    return _mapConnectivityResult(results);
  }

  @override
  Future<NetworkStatus> get networkStatus async {
    final results = await _connectivity.checkConnectivity();
    final hasConnection = _hasConnection(results);
    final type = _mapConnectivityResult(results);

    if (hasConnection) {
      return NetworkStatus.connected(type);
    } else {
      return NetworkStatus.disconnected();
    }
  }

  @override
  Stream<NetworkStatus> get onNetworkStatusChanged {
    _statusController ??= StreamController<NetworkStatus>.broadcast(
      onListen: _startListening,
      onCancel: _stopListening,
    );
    return _statusController!.stream;
  }

  void _startListening() {
    _subscription = _connectivity.onConnectivityChanged.listen(
      (results) {
        final hasConnection = _hasConnection(results);
        final type = _mapConnectivityResult(results);
        final status = hasConnection
            ? NetworkStatus.connected(type)
            : NetworkStatus.disconnected();
        _statusController?.add(status);
      },
      onError: (error) {
        _statusController?.add(NetworkStatus.disconnected());
      },
    );
  }

  void _stopListening() {
    _subscription?.cancel();
    _subscription = null;
  }

  bool _hasConnection(List<ConnectivityResult> results) {
    if (results.isEmpty) return false;
    return !results.contains(ConnectivityResult.none);
  }

  NetworkConnectionType _mapConnectivityResult(
    List<ConnectivityResult> results,
  ) {
    if (results.isEmpty || results.contains(ConnectivityResult.none)) {
      return NetworkConnectionType.none;
    }

    // Priority order: WiFi > Ethernet > Mobile > Bluetooth > VPN
    if (results.contains(ConnectivityResult.wifi)) {
      return NetworkConnectionType.wifi;
    } else if (results.contains(ConnectivityResult.ethernet)) {
      return NetworkConnectionType.ethernet;
    } else if (results.contains(ConnectivityResult.mobile)) {
      return NetworkConnectionType.mobile;
    } else if (results.contains(ConnectivityResult.bluetooth)) {
      return NetworkConnectionType.bluetooth;
    } else if (results.contains(ConnectivityResult.vpn)) {
      return NetworkConnectionType.vpn;
    } else {
      return NetworkConnectionType.unknown;
    }
  }

  /// Dispose resources
  void dispose() {
    _subscription?.cancel();
    _statusController?.close();
  }
}

// ============================================================================
// Riverpod Providers
// ============================================================================

/// Provider for Connectivity instance
@riverpod
Connectivity connectivity(Ref ref) {
  return Connectivity();
}

/// Provider for NetworkInfo instance
@riverpod
NetworkInfo networkInfo(Ref ref) {
  final connectivity = ref.watch(connectivityProvider);
  final networkInfo = NetworkInfoImpl(connectivity);

  // Dispose when provider is disposed
  ref.onDispose(() {
    networkInfo.dispose();
  });

  return networkInfo;
}

/// Provider for current network connection status (boolean)
@riverpod
Future<bool> isConnected(Ref ref) async {
  final networkInfo = ref.watch(networkInfoProvider);
  return await networkInfo.isConnected;
}

/// Provider for current network connection type
@riverpod
Future<NetworkConnectionType> connectionType(Ref ref) async {
  final networkInfo = ref.watch(networkInfoProvider);
  return await networkInfo.connectionType;
}

/// Stream provider for network status changes
@riverpod
Stream<NetworkStatus> networkStatusStream(Ref ref) {
  final networkInfo = ref.watch(networkInfoProvider);
  return networkInfo.onNetworkStatusChanged;
}

/// Provider for current network status
@riverpod
class NetworkStatusNotifier extends _$NetworkStatusNotifier {
  @override
  Future<NetworkStatus> build() async {
    final networkInfo = ref.watch(networkInfoProvider);
    final status = await networkInfo.networkStatus;

    // Listen to network changes
    ref.listen(networkStatusStreamProvider, (_, next) {
      next.whenData((newStatus) {
        state = AsyncValue.data(newStatus);
      });
    });

    return status;
  }

  /// Manually refresh network status
  Future<void> refresh() async {
    state = const AsyncValue.loading();
    final networkInfo = ref.read(networkInfoProvider);
    state = await AsyncValue.guard(() => networkInfo.networkStatus);
  }

  /// Check if connected
  bool get isConnected {
    return state.when(
      data: (status) => status.isConnected,
      loading: () => false,
      error: (_, __) => false,
    );
  }

  /// Get connection type
  NetworkConnectionType get type {
    return state.when(
      data: (status) => status.connectionType,
      loading: () => NetworkConnectionType.none,
      error: (_, __) => NetworkConnectionType.none,
    );
  }
}

// ============================================================================
// Utility Extensions
// ============================================================================

/// Extension methods for NetworkConnectionType
extension NetworkConnectionTypeX on NetworkConnectionType {
  /// Get display name in Vietnamese
  String get displayNameVi {
    switch (this) {
      case NetworkConnectionType.wifi:
        return 'WiFi';
      case NetworkConnectionType.mobile:
        return 'Dữ liệu di động';
      case NetworkConnectionType.ethernet:
        return 'Ethernet';
      case NetworkConnectionType.bluetooth:
        return 'Bluetooth';
      case NetworkConnectionType.vpn:
        return 'VPN';
      case NetworkConnectionType.none:
        return 'Không có kết nối';
      case NetworkConnectionType.unknown:
        return 'Không xác định';
    }
  }

  /// Get display name in English
  String get displayNameEn {
    switch (this) {
      case NetworkConnectionType.wifi:
        return 'WiFi';
      case NetworkConnectionType.mobile:
        return 'Mobile Data';
      case NetworkConnectionType.ethernet:
        return 'Ethernet';
      case NetworkConnectionType.bluetooth:
        return 'Bluetooth';
      case NetworkConnectionType.vpn:
        return 'VPN';
      case NetworkConnectionType.none:
        return 'No Connection';
      case NetworkConnectionType.unknown:
        return 'Unknown';
    }
  }

  /// Check if this is a valid connection type
  bool get isValid {
    return this != NetworkConnectionType.none &&
        this != NetworkConnectionType.unknown;
  }
}
