import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:frontend_eccp_mobile/app/core/constants/enum/location_status_enum.dart';
import 'package:frontend_eccp_mobile/app/core/services/bottom_sheet_service.dart';
import 'package:frontend_eccp_mobile/app/core/utils/token_manager.dart';
import 'package:geolocator/geolocator.dart';

class LocationPermissionService {
  LocationPermissionService._();

  static Future<SimpleLocationStatus> checkStatus() async {
    final serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) return SimpleLocationStatus.serviceDisabled;

    final perm = await Geolocator.checkPermission();
    switch (perm) {
      case LocationPermission.always:
      case LocationPermission.whileInUse:
        return SimpleLocationStatus.granted;
      case LocationPermission.denied:
      case LocationPermission.unableToDetermine:
        return SimpleLocationStatus.denied;
      case LocationPermission.deniedForever:
        return SimpleLocationStatus.deniedForever;
    }
  }

  static bool _requesting = false;
  static Completer<void>? _waiter;

  static Future<T> _withLock<T>(Future<T> Function() body) async {
    while (_requesting) {
      _waiter ??= Completer<void>();
      await _waiter!.future;
    }
    _requesting = true;
    try {
      return await body();
    } finally {
      _requesting = false;
      _waiter?.complete();
      _waiter = null;
    }
  }

  static Future<bool> request() => _withLock<bool>(() async {
    var p = await Geolocator.checkPermission();
    if (p == LocationPermission.always || p == LocationPermission.whileInUse) {
      return true;
    }

    if (p == LocationPermission.deniedForever) {
      return false;
    }

    p = await Geolocator.requestPermission();
    return p == LocationPermission.always || p == LocationPermission.whileInUse;
  });

  static Future<void> ensure({
    required VoidCallback onGranted,
    VoidCallback? onNotGranted,
    bool showSheetIfDenied = true,
    bool returnImmediatelyOnDenied = false,
    String? onlyOnceKey,
  }) async {
    final suppressSheet =
        (showSheetIfDenied && onlyOnceKey != null) &&
        await TokenManager.getOneTimeFlag(onlyOnceKey);

    final serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      onNotGranted?.call();
      if (showSheetIfDenied && !suppressSheet) {
        final f = _showGoToSettingsSheet(
          forServiceDisabled: true,
          onAfterReturn: onGranted,
          onlyOnceKey: onlyOnceKey,
          allowRequestNow: false,
        );
        if (returnImmediatelyOnDenied) {
          unawaited(f);
          return;
        }
        await f;
      }
      return;
    }

    final grantedNow = await request();
    if (grantedNow) {
      onGranted();
      return;
    }

    onNotGranted?.call();

    if (showSheetIfDenied && !suppressSheet) {
      final f = _showGoToSettingsSheet(
        forServiceDisabled: false,
        onAfterReturn: onGranted,
        onlyOnceKey: onlyOnceKey,
      );
      if (returnImmediatelyOnDenied) {
        unawaited(f);
        return;
      }
      await f;
    }
  }

  static Future<void> showGoToSettingsSheet({
    required VoidCallback onAfterReturn,
    bool forServiceDisabled = false,
    bool allowRequestNow = true,
    String? onlyOnceKey,
  }) async {
    await _showGoToSettingsSheet(
      onAfterReturn: onAfterReturn,
      forServiceDisabled: forServiceDisabled,
      allowRequestNow: allowRequestNow,
      onlyOnceKey: onlyOnceKey,
    );
  }

  static Future<bool> recheckGranted() async =>
      (await checkStatus()) == SimpleLocationStatus.granted;

  static Future<void> _showGoToSettingsSheet({
    required VoidCallback onAfterReturn,
    required bool forServiceDisabled,
    bool allowRequestNow = true,
    String? onlyOnceKey,
  }) async {
    if (onlyOnceKey != null) {
      await TokenManager.setOneTimeFlag(onlyOnceKey, value: true);
    }

    await BottomSheetService.showLocationPermission(
      onOpenSettings: () async {
        await Future<dynamic>.delayed(const Duration(milliseconds: 100));
        if (await recheckGranted()) onAfterReturn();
      },
      onRequestNow: allowRequestNow
          ? () async {
              final ok = await request();
              if (ok) onAfterReturn();
            }
          : null,
    );
  }
}
