import 'dart:async';
import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:hmg_qline/config/dependency_injection.dart';
import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/models/global_config_model.dart';
import 'package:hmg_qline/models/ticket_model.dart';
import 'package:hmg_qline/repositories/screen_details_repo.dart';
import 'package:hmg_qline/repositories/signalR_repo.dart';
import 'package:hmg_qline/services/audio_service.dart';
import 'package:hmg_qline/services/cache_service.dart';
import 'package:hmg_qline/services/logger_service.dart';
import 'package:hmg_qline/services/text_to_speech_service.dart';
import 'package:hmg_qline/services/crash_handler_service.dart';
import 'package:hmg_qline/utilities/enums.dart';
import 'package:hmg_qline/view_models/screen_config_view_model.dart';

class QueuingViewModel extends ChangeNotifier {
  final ScreenDetailsRepo screenDetailsRepo;
  final SignalrRepo signalrRepo;
  final CacheService cacheService;
  final AudioService audioService;
  final TextToSpeechService textToSpeechService;
  final LoggerService loggerService;

  QueuingViewModel({
    required this.screenDetailsRepo,
    required this.signalrRepo,
    required this.cacheService,
    required this.audioService,
    required this.textToSpeechService,
    required this.loggerService,
  });

  Future<void> initializeQueueingVM() async {
    try {
      await startHubConnection();
      initializeAudioPlayer();
      initializeTextToSpeech();
    } catch (e, st) {
      await getIt.get<CrashHandlerService>().handleCrash(error: e, stackTrace: st, context: 'initializeQueueingVM -> queuing_view_model.dart');
    }
  }

  Future<bool?> startHubConnection() async {
    try {
      ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
      return await signalrRepo.startHubConnection(
        deviceIp: screenConfigViewModel.currentScreenIP,
        onHubTicketCall: onHubTicketCall,
        onHubConfigCall: onHubConfigCall,
        onHubReconnected: onHubReconnected,
        onHubDisconnected: onHubDisconnected,
        onHubConnecting: onHubConnecting,
      );
    } catch (e, st) {
      await getIt.get<CrashHandlerService>().handleCrash(error: e, stackTrace: st, context: 'startHubConnection -> queuing_view_model.dart');
      return false;
    }
  }

  Future<void> stopHubConnection() async {
    return await signalrRepo.closeConnection();
  }

  initializeAudioPlayer() {
    audioService.listenAudioPlayerEvents(onToneCompleted: onToneCompleted);
  }

  initializeTextToSpeech() {
    textToSpeechService.listenToTextToSpeechEvents(onVoiceCompleted: onVoiceCompleted);
  }

  Future<void> onHubConfigCall(var response) async {
    loggerService.logToFile(message: response.toString(), source: "onHubConfigCall -> queueing_view_model.dart ", type: LogTypeEnum.data);

    if (response != null && response.isNotEmpty) {
      var data = response.first['data'];
      GlobalConfigurationsModel globalConfigurationsModel = GlobalConfigurationsModel.fromJson(
        json: data,
        qType: response.first['qType'] ?? 1,
        screenType: response.first['screenType'] ?? 1,
      );

      log("onHubConfigCall: ${globalConfigurationsModel.toString()}");

      ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
      screenConfigViewModel.updateGlobalConfigurationsModel(value: globalConfigurationsModel, needNotify: true, shouldUpdateNextPrayer: true);
    }
  }

  Future<void> onHubTicketCall(List<Object?>? response) async {
    try {
      loggerService.logToFile(message: response.toString(), source: "onHubTicketCall -> queueing_view_model.dart ", type: LogTypeEnum.data);

      log("onHubTicketCall: $response");

      log("isCallingInProgress: $isCallingInProgress");
      if (response != null && response.isNotEmpty) {
        TicketDetailsModel ticketDetailsModel = TicketDetailsModel.fromJson(response.first as Map<String, dynamic>);
        addNewTicket(ticketDetailsModel);
      }
    } catch (e, st) {
      await getIt.get<CrashHandlerService>().handleCrash(error: e, stackTrace: st, context: 'onHubTicketCall -> queuing_view_model.dart');
    }
  }

  Future<void> onHubReconnected(var response) async {
    log("onHubConnected: $response");
    loggerService.logToFile(message: response.toString(), source: "onHubReconnected -> queueing_view_model.dart ", type: LogTypeEnum.connectivity);

    ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
    screenConfigViewModel.updateIsHubConnected(true);
    screenConfigViewModel.notifyListeners();
  }

  Future<void> onHubDisconnected(var response) async {
    log("onHubDisconnected: $response");
    loggerService.logToFile(message: response.toString(), source: "onHubDisconnected -> queueing_view_model.dart ", type: LogTypeEnum.connectivity);
    ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
    screenConfigViewModel.updateIsHubConnected(false);
    screenConfigViewModel.notifyListeners();
  }

  Future<void> onHubConnecting(var response) async => log("onHubConnecting: $response");

  Future<void> onToneCompleted() async {
    GlobalConfigurationsModel globalConfigurationsModel = getIt.get<ScreenConfigViewModel>().globalConfigurationsModel;
    await textToSpeechService.speechText(globalConfigurationsModel: globalConfigurationsModel, ticket: currentTickets.first, isMute: !(globalConfigurationsModel.isVoiceReq));
  }

  Future<void> onVoiceCompleted() async {
    log("isSpeechCompleted::: $isSpeechCompleted");
    if (isSpeechCompleted) {
      waitAndCallNextTicketIfAvailable();
    }
  }

  waitAndCallNextTicketIfAvailable() {
    GlobalConfigurationsModel globalConfigurationsModel = getIt.get<ScreenConfigViewModel>().globalConfigurationsModel;
    Timer(Duration(seconds: globalConfigurationsModel.concurrentCallDelaySec), () async {
      if (queueTickets.isNotEmpty) {
        int index = itemAlreadyAvailableAtIndex(ticketToSearch: queueTickets.first, listToSearchIn: currentTickets);
        if (index != -1) {
          currentTickets.removeAt(index);
        }
        currentTickets.insert(0, queueTickets.first);

        if (currentTickets.length > globalConfigurationsModel.screenMaxDisplayPatients) {
          currentTickets.removeLast();
        }
        notifyListeners();
        queueTickets.removeAt(0);
        callTicketOnScreen(ticketData: currentTickets.first.ticketModel);
      } else {
        isCallingInProgress = false;
      }
    });
  }

  int itemAlreadyAvailableAtIndex({required TicketDetailsModel ticketToSearch, required List<TicketDetailsModel> listToSearchIn}) {
    int isFoundAt = -1;
    try {
      isFoundAt = listToSearchIn.indexWhere((ticket) {
        log("ticket.ticketModel!.queueNo: ${ticket.ticketModel!.queueNo}");
        log("ticketToSearch.ticketModel!.queueNo: ${ticketToSearch.ticketModel!.queueNo}");
        return ticket.ticketModel!.queueNo == ticketToSearch.ticketModel!.queueNo;
      });
      log("itemAlreadyAvailableAtIndex: $isFoundAt");
      return isFoundAt;
    } catch (e) {
      log("Error isItemAlreadyAvailableInList: ${e.toString()}");
      return isFoundAt;
    }
  }

  bool isCallingInProgress = false;

  // Tickets Management
  List<TicketDetailsModel> currentTickets = [];
  List<TicketDetailsModel> queueTickets = [];

  void addNewTicket(TicketDetailsModel ticket) {
    if (!isCallingInProgress) {
      int index = itemAlreadyAvailableAtIndex(ticketToSearch: ticket, listToSearchIn: currentTickets);
      if (index != -1) {
        currentTickets.removeAt(index);
      }
      currentTickets.insert(0, ticket);

      GlobalConfigurationsModel globalConfigurationsModel = getIt.get<ScreenConfigViewModel>().globalConfigurationsModel;
      if (currentTickets.length > globalConfigurationsModel.screenMaxDisplayPatients) {
        currentTickets.removeLast();
      }
      callTicketOnScreen(ticketData: currentTickets.first.ticketModel);
    } else {
      int index = itemAlreadyAvailableAtIndex(ticketToSearch: ticket, listToSearchIn: queueTickets);
      if (index != -1) {
        queueTickets.removeAt(index);
      }
      queueTickets.add(ticket);
      log("inQueue Length: ${queueTickets.length}");
    }
    notifyListeners();
  }

  Future<void> callTicketOnScreen({required TicketData? ticketData}) async {
    if (ticketData == null) return;
    ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
    GlobalConfigurationsModel globalConfigurationsModel = screenConfigViewModel.globalConfigurationsModel;
    if (globalConfigurationsModel.qTypeEnum == QTypeEnum.appointment) {
      screenConfigViewModel.acknowledgeTicketForAppointmentOnly(
        ticketQueueID: ticketData.id ?? 0,
        ipAddress: screenConfigViewModel.currentScreenIP,
        callTypeEnum: ticketData.callTypeEnum,
      );
    } else {
      screenConfigViewModel.acknowledgeTicket(ticketQueueID: ticketData.id ?? 0,
        ipAddress: screenConfigViewModel.currentScreenIP,
      );
    }

    log("globalConfigurationsModel: ${globalConfigurationsModel.toString()}");
    isCallingInProgress = true;
    await audioService.playTone(path: AppAssets.callTone, isMute: !(globalConfigurationsModel.isToneReq));
  }

  List<List<int>> memoryHog = [];

  void triggerOOM() {
    while (true) {
      // Repeatedly add large chunks (e.g., 10 million integers) to a list
      // This prevents the Garbage Collector (GC) from reclaiming the memory
      memoryHog.add(List.filled(10000000, 9));
    }
  }

  /// Throws an exception from an async context (await) to test runZonedGuarded handling.
  Future<void> triggerAsyncCrash() async {
    await Future.delayed(const Duration(milliseconds: 50));
    throw Exception('Test asynchronous crash from QueuingViewModel.triggerAsyncCrash');
  }

  /// Creates an unawaited Future that throws to simulate an unhandled async error.
  void triggerUnhandledFutureCrash() {
    Future.delayed(const Duration(milliseconds: 50), () {
      throw Exception('Test unhandled future crash from QueuingViewModel.triggerUnhandledFutureCrash');
    });
  }
}
