// water_heater_bloc.dart

import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
import 'package:syncrow_web/utils/format_date_time.dart';

part 'water_heater_event.dart';
part 'water_heater_state.dart';

class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
  WaterHeaterBloc() : super(WaterHeaterInitial()) {
    on<WaterHeaterFetchStatusEvent>(_fetchWaterHeaterStatus);
    on<ToggleWaterHeaterEvent>(_controlWaterHeater);
    on<FetchWaterHeaterBatchStatusEvent>(_batchFetchWaterHeater);
    on<ControlWaterHeaterBatchEvent>(_batchControlWaterHeater);
    on<UpdateScheduleEvent>(_updateScheduleEvent);
    on<StopScheduleEvent>(_stopScheduleEvent);
    on<DecrementCountdownEvent>(_onDecrementCountdown);
    on<InitializeAddScheduleEvent>(_initializeAddSchedule);
    on<UpdateSelectedTimeEvent>(_updateSelectedTime);
    on<UpdateSelectedDayEvent>(_updateSelectedDay);
    on<UpdateFunctionOnEvent>(_updateFunctionOn);

    on<GetSchedulesEvent>(_getSchedule);
    on<AddScheduleEvent>(_onAddSchedule);
    on<EditWaterHeaterScheduleEvent>(_onEditSchedule);
    on<DeleteScheduleEvent>(_onDeleteSchedule);
    on<UpdateScheduleEntryEvent>(_onUpdateSchedule);
    on<StatusUpdated>(_onStatusUpdated);
  }

  late WaterHeaterStatusModel deviceStatus;
  Timer? _countdownTimer;
  // Timer? _inchingTimer;

  FutureOr<void> _initializeAddSchedule(
    InitializeAddScheduleEvent event,
    Emitter<WaterHeaterState> emit,
  ) {
    final currentState = state as WaterHeaterDeviceStatusLoaded;
    if (event.isEditing) {
      emit(currentState.copyWith(
        selectedTime: event.selectedTime,
        selectedDays: event.selectedDays ?? List.filled(7, false),
        functionOn: event.functionOn ?? false,
        isEditing: event.isEditing,
      ));
    } else {
      emit(currentState.copyWith(
        selectedTime: null,
        selectedDays: List.filled(7, false),
        functionOn: false,
        isEditing: false,
      ));
    }
  }

  FutureOr<void> _updateSelectedTime(
    UpdateSelectedTimeEvent event,
    Emitter<WaterHeaterState> emit,
  ) {
    final currentState = state as WaterHeaterDeviceStatusLoaded;

    emit(currentState.copyWith(selectedTime: event.selectedTime));
  }

  FutureOr<void> _updateSelectedDay(
    UpdateSelectedDayEvent event,
    Emitter<WaterHeaterState> emit,
  ) {
    final currentState = state as WaterHeaterDeviceStatusLoaded;
    final updatedDays = List<bool>.from(currentState.selectedDays);
    updatedDays[event.index] = event.value;
    emit(currentState.copyWith(
        selectedDays: updatedDays, selectedTime: currentState.selectedTime));
  }

  FutureOr<void> _updateFunctionOn(
    UpdateFunctionOnEvent event,
    Emitter<WaterHeaterState> emit,
  ) {
    final currentState = state as WaterHeaterDeviceStatusLoaded;
    emit(currentState.copyWith(
        functionOn: event.isOn, selectedTime: currentState.selectedTime));
  }

  FutureOr<void> _updateScheduleEvent(
    UpdateScheduleEvent event,
    Emitter<WaterHeaterState> emit,
  ) async {
    final currentState = state;
    if (currentState is WaterHeaterDeviceStatusLoaded) {
      if (event.scheduleMode == ScheduleModes.schedule) {
        emit(currentState.copyWith(
          scheduleMode: ScheduleModes.schedule,
        ));
      }
      if (event.scheduleMode == ScheduleModes.countdown) {
        final countdownRemaining =
            Duration(hours: event.hours, minutes: event.minutes);

        emit(currentState.copyWith(
          scheduleMode: ScheduleModes.countdown,
          countdownHours: countdownRemaining.inHours,
          countdownMinutes: countdownRemaining.inMinutes % 60,
          isCountdownActive: currentState.isCountdownActive,
          countdownRemaining: countdownRemaining,
        ));

        if (!currentState.isCountdownActive! &&
            countdownRemaining > Duration.zero) {
          _startCountdownTimer(emit, countdownRemaining);
        }
      } else if (event.scheduleMode == ScheduleModes.inching) {
        final inchingDuration =
            Duration(hours: event.hours, minutes: event.minutes);

        emit(currentState.copyWith(
          scheduleMode: ScheduleModes.inching,
          inchingHours: inchingDuration.inHours,
          inchingMinutes: inchingDuration.inMinutes % 60,
          isInchingActive: currentState.isInchingActive,
        ));
      }
    }
  }

  FutureOr<void> _controlWaterHeater(
    ToggleWaterHeaterEvent event,
    Emitter<WaterHeaterState> emit,
  ) async {
    if (state is WaterHeaterDeviceStatusLoaded) {
      final currentState = state as WaterHeaterDeviceStatusLoaded;

      final oldValue = _getValueByCode(event.code);

      _updateLocalValue(event.code, event.value);

      emit(currentState.copyWith(
        status: deviceStatus,
      ));

      final success = await _runDebounce(
        deviceId: event.deviceId,
        code: event.code,
        value: event.value,
        oldValue: oldValue,
        emit: emit,
        isBatch: false,
      );

      if (success) {
        if (event.code == "countdown_1") {
          final countdownDuration = Duration(seconds: event.value);

          emit(currentState.copyWith(
            countdownHours: countdownDuration.inHours,
            countdownMinutes: countdownDuration.inMinutes % 60,
            countdownRemaining: countdownDuration,
            isCountdownActive: true,
          ));

          if (countdownDuration.inSeconds > 0) {
            _startCountdownTimer(emit, countdownDuration);
          } else {
            _countdownTimer?.cancel();
            emit(currentState.copyWith(
              countdownHours: 0,
              countdownMinutes: 0,
              countdownRemaining: Duration.zero,
              isCountdownActive: false,
            ));
          }
        } else if (event.code == "switch_inching") {
          final inchingDuration = Duration(seconds: event.value);
          //if (inchingDuration.inSeconds > 0) {
          // _startInchingTimer(emit, inchingDuration);
          //  } else {
          emit(currentState.copyWith(
            inchingHours: inchingDuration.inHours,
            inchingMinutes: inchingDuration.inMinutes % 60,
            isInchingActive: true,
          ));
          // }
        }
      }
    }
  }

  FutureOr<void> _stopScheduleEvent(
    StopScheduleEvent event,
    Emitter<WaterHeaterState> emit,
  ) async {
    if (state is WaterHeaterDeviceStatusLoaded) {
      final currentState = state as WaterHeaterDeviceStatusLoaded;
      final isCountDown = currentState.scheduleMode == ScheduleModes.countdown;

      _countdownTimer?.cancel();

      if (isCountDown) {
        emit(currentState.copyWith(
          countdownHours: 0,
          countdownMinutes: 0,
          countdownRemaining: Duration.zero,
          isCountdownActive: false,
        ));
      } else if (currentState.scheduleMode == ScheduleModes.inching) {
        emit(currentState.copyWith(
          inchingHours: 0,
          inchingMinutes: 0,
          isInchingActive: false,
        ));
      }

      try {
        final status = await DevicesManagementApi().deviceControl(
          event.deviceId,
          Status(
              code: isCountDown ? 'countdown_1' : 'switch_inching', value: 0),
        );
        if (!status) {
          emit(const WaterHeaterFailedState(error: 'Failed to stop schedule.'));
        }
      } catch (e) {
        emit(WaterHeaterFailedState(error: e.toString()));
      }
    }
  }

  FutureOr<void> _fetchWaterHeaterStatus(
    WaterHeaterFetchStatusEvent event,
    Emitter<WaterHeaterState> emit,
  ) async {
    emit(WaterHeaterLoadingState());

    try {
      final status =
          await DevicesManagementApi().getDeviceStatus(event.deviceId);
      deviceStatus =
          WaterHeaterStatusModel.fromJson(event.deviceId, status.status);

      if (deviceStatus.scheduleMode == ScheduleModes.countdown) {
        final countdownRemaining = Duration(
          hours: deviceStatus.countdownHours,
          minutes: deviceStatus.countdownMinutes,
        );

        if (countdownRemaining > Duration.zero) {
          emit(WaterHeaterDeviceStatusLoaded(
            deviceStatus,
            scheduleMode: ScheduleModes.countdown,
            countdownHours: deviceStatus.countdownHours,
            countdownMinutes: deviceStatus.countdownMinutes,
            isCountdownActive: true,
            countdownRemaining: countdownRemaining,
          ));
          _startCountdownTimer(emit, countdownRemaining);
        } else {
          emit(WaterHeaterDeviceStatusLoaded(
            deviceStatus,
            scheduleMode: ScheduleModes.countdown,
            countdownHours: 0,
            countdownMinutes: 0,
            isCountdownActive: false,
            countdownRemaining: Duration.zero,
          ));
        }
      } else if (deviceStatus.scheduleMode == ScheduleModes.inching) {
        final inchingDuration = Duration(
          hours: deviceStatus.inchingHours,
          minutes: deviceStatus.inchingMinutes,
        );

        if (inchingDuration > Duration.zero) {
          emit(WaterHeaterDeviceStatusLoaded(
            deviceStatus,
            scheduleMode: ScheduleModes.inching,
            inchingHours: deviceStatus.inchingHours,
            inchingMinutes: deviceStatus.inchingMinutes,
            isInchingActive: true,
          ));
//_startInchingTimer(emit, inchingDuration);
        } else {
          emit(WaterHeaterDeviceStatusLoaded(
            deviceStatus,
            scheduleMode: ScheduleModes.inching,
            inchingHours: 0,
            inchingMinutes: 0,
            isInchingActive: false,
          ));
        }
      } else {
        emit(WaterHeaterDeviceStatusLoaded(
          deviceStatus,
          scheduleMode: deviceStatus.scheduleMode,
          countdownHours: 0,
          countdownMinutes: 0,
          inchingHours: 0,
          inchingMinutes: 0,
          isCountdownActive: false,
          isInchingActive: false,
        ));
      }
      _listenToChanges(event.deviceId);
    } catch (e) {
      emit(WaterHeaterFailedState(error: e.toString()));
    }
  }

  _listenToChanges(deviceId) {
    try {
      DatabaseReference ref =
          FirebaseDatabase.instance.ref('device-status/$deviceId');
      Stream<DatabaseEvent> stream = ref.onValue;

      stream.listen((DatabaseEvent event) {
        Map<dynamic, dynamic> usersMap =
            event.snapshot.value as Map<dynamic, dynamic>;

        List<Status> statusList = [];
        usersMap['status'].forEach((element) {
          statusList
              .add(Status(code: element['code'], value: element['value']));
        });

        deviceStatus = WaterHeaterStatusModel.fromJson(
            usersMap['productUuid'], statusList);
        if (!isClosed) {
          add(StatusUpdated(deviceStatus));
        }
      });
    } catch (_) {}
  }

  void _onStatusUpdated(StatusUpdated event, Emitter<WaterHeaterState> emit) {
    deviceStatus = event.deviceStatus;
    emit(WaterHeaterDeviceStatusLoaded(deviceStatus));
  }

  void _startCountdownTimer(
    Emitter<WaterHeaterState> emit,
    Duration countdownRemaining,
  ) {
    _countdownTimer?.cancel();

    _countdownTimer = Timer.periodic(const Duration(minutes: 1), (timer) {
      add(DecrementCountdownEvent());
    });
  }

  // void _startInchingTimer(
  //   Emitter<WaterHeaterState> emit,
  //   Duration inchingDuration,
  // ) {
  //   _inchingTimer?.cancel();

  //   _inchingTimer = Timer.periodic(const Duration(minutes: 1), (timer) {
  //     add(DecrementInchingEvent());
  //   });
  // }

  _onDecrementCountdown(
    DecrementCountdownEvent event,
    Emitter<WaterHeaterState> emit,
  ) {
    if (state is WaterHeaterDeviceStatusLoaded) {
      final currentState = state as WaterHeaterDeviceStatusLoaded;

      if (currentState.countdownRemaining != null &&
          currentState.countdownRemaining! > Duration.zero) {
        final newRemaining =
            currentState.countdownRemaining! - const Duration(minutes: 1);

        if (newRemaining <= Duration.zero) {
          _countdownTimer?.cancel();
          emit(currentState.copyWith(
            countdownHours: 0,
            countdownMinutes: 0,
            isCountdownActive: false,
            countdownRemaining: Duration.zero,
          ));
          return;
        }

        int totalSeconds = newRemaining.inSeconds;

        int newHours = totalSeconds ~/ 3600;
        int newMinutes = (totalSeconds % 3600) ~/ 60;

        emit(currentState.copyWith(
          countdownHours: newHours,
          countdownMinutes: newMinutes,
          countdownRemaining: newRemaining,
        ));
      }
    }
  }

  // FutureOr<void> _onDecrementInching(
  //   DecrementInchingEvent event,
  //   Emitter<WaterHeaterState> emit,
  // ) {
  //   if (state is WaterHeaterDeviceStatusLoaded) {
  //     final currentState = state as WaterHeaterDeviceStatusLoaded;

  //     if (currentState.inchingHours > 0 || currentState.inchingMinutes > 0) {
  //       final newRemaining = Duration(
  //             hours: currentState.inchingHours,
  //             minutes: currentState.inchingMinutes,
  //           ) -
  //           const Duration(minutes: 1);

  //       if (newRemaining <= Duration.zero) {
  //         _inchingTimer?.cancel();
  //         emit(currentState.copyWith(
  //           inchingHours: 0,
  //           inchingMinutes: 0,
  //           isInchingActive: false,
  //         ));
  //       } else {
  //         emit(currentState.copyWith(
  //           inchingHours: newRemaining.inHours,
  //           inchingMinutes: newRemaining.inMinutes % 60,
  //         ));
  //       }
  //     }
  //   }
  // }

  Future<bool> _runDebounce({
    required dynamic deviceId,
    required String code,
    required dynamic value,
    required dynamic oldValue,
    required Emitter<WaterHeaterState> emit,
    required bool isBatch,
  }) async {
    try {
      late bool status;
      await Future.delayed(const Duration(milliseconds: 500));

      if (isBatch) {
        status = await DevicesManagementApi().deviceBatchControl(
          deviceId,
          code,
          value,
        );
      } else {
        status = await DevicesManagementApi().deviceControl(
          deviceId,
          Status(code: code, value: value),
        );
      }

      if (!status) {
        _revertValue(code, oldValue, emit.call);
        return false;
      } else {
        return true;
      }
    } catch (e) {
      _revertValue(code, oldValue, emit.call);
      return false;
    }
  }

  void _revertValue(String code, dynamic oldValue,
      void Function(WaterHeaterState state) emit) {
    _updateLocalValue(code, oldValue);
    if (state is WaterHeaterDeviceStatusLoaded) {
      final currentState = state as WaterHeaterDeviceStatusLoaded;
      emit(currentState.copyWith(
        status: deviceStatus,
      ));
    }
  }

  void _updateLocalValue(String code, dynamic value) {
    switch (code) {
      case 'switch_1':
        if (value is bool) {
          deviceStatus = deviceStatus.copyWith(heaterSwitch: value);
        }
        break;
      case 'countdown_1':
        if (value is int) {
          deviceStatus = deviceStatus.copyWith(
            countdownHours: value ~/ 60,
            countdownMinutes: value % 60,
          );
        }
        break;
      default:
        break;
    }
  }

  dynamic _getValueByCode(String code) {
    switch (code) {
      case 'switch_1':
        return deviceStatus.heaterSwitch;
      case 'countdown_1':
        return deviceStatus.countdownHours * 60 + deviceStatus.countdownMinutes;
      default:
        return null;
    }
  }

  @override
  Future<void> close() {
    _countdownTimer?.cancel();
    return super.close();
  }

  FutureOr<void> _getSchedule(
      GetSchedulesEvent event, Emitter<WaterHeaterState> emit) async {
    emit(ScheduleLoadingState());

    try {
      List<ScheduleModel> schedules = await DevicesManagementApi()
          .getDeviceSchedules(deviceStatus.uuid, event.category);

      emit(WaterHeaterDeviceStatusLoaded(
        deviceStatus,
        schedules: schedules,
        scheduleMode: ScheduleModes.schedule,
      ));
    } catch (e) {
      //(const WaterHeaterFailedState(error: 'Failed to fetch schedules.'));
      emit(WaterHeaterDeviceStatusLoaded(
        deviceStatus,
        schedules: const [],
      ));
    }
  }

  FutureOr<void> _onAddSchedule(
    AddScheduleEvent event,
    Emitter<WaterHeaterState> emit,
  ) async {
    if (state is WaterHeaterDeviceStatusLoaded) {
      final currentState = state as WaterHeaterDeviceStatusLoaded;

      ScheduleEntry newSchedule = ScheduleEntry(
        category: event.category,
        time: formatTimeOfDayToISO(event.time),
        function: Status(code: 'switch_1', value: event.functionOn),
        days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
      );

      // emit(ScheduleLoadingState());

      bool success = await DevicesManagementApi()
          .addScheduleRecord(newSchedule, currentState.status.uuid);

      if (success) {
        add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid));
      } else {
        emit(currentState);
        //emit(const WaterHeaterFailedState(error: 'Failed to add schedule.'));
      }
    }
  }

  FutureOr<void> _onEditSchedule(EditWaterHeaterScheduleEvent event,
      Emitter<WaterHeaterState> emit) async {
    if (state is WaterHeaterDeviceStatusLoaded) {
      final currentState = state as WaterHeaterDeviceStatusLoaded;

      ScheduleEntry newSchedule = ScheduleEntry(
        scheduleId: event.scheduleId,
        category: event.category,
        time: formatTimeOfDayToISO(event.time),
        function: Status(code: 'switch_1', value: event.functionOn),
        days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
      );

      // emit(ScheduleLoadingState());

      bool success = await DevicesManagementApi().editScheduleRecord(
        currentState.status.uuid,
        newSchedule,
      );

      if (success) {
        add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid));
      } else {
        emit(currentState);
        //emit(const WaterHeaterFailedState(error: 'Failed to add schedule.'));
      }
    }
  }

  FutureOr<void> _onUpdateSchedule(
    UpdateScheduleEntryEvent event,
    Emitter<WaterHeaterState> emit,
  ) async {
    if (state is WaterHeaterDeviceStatusLoaded) {
      final currentState = state as WaterHeaterDeviceStatusLoaded;

      final updatedSchedules = currentState.schedules.map((schedule) {
        if (schedule.scheduleId == event.scheduleId) {
          return schedule.copyWith(
            function: Status(code: 'switch_1', value: event.functionOn),
            enable: event.enable,
          );
        }
        return schedule;
      }).toList();

      bool success = await DevicesManagementApi().updateScheduleRecord(
        enable: event.enable,
        uuid: currentState.status.uuid,
        scheduleId: event.scheduleId,
      );

      if (success) {
        emit(currentState.copyWith(schedules: updatedSchedules));
      } else {
        emit(currentState);
        // emit(const WaterHeaterFailedState(error: 'Failed to update schedule.'));
      }
    }
  }

  FutureOr<void> _onDeleteSchedule(
    DeleteScheduleEvent event,
    Emitter<WaterHeaterState> emit,
  ) async {
    if (state is WaterHeaterDeviceStatusLoaded) {
      final currentState = state as WaterHeaterDeviceStatusLoaded;

      // emit(ScheduleLoadingState());

      bool success = await DevicesManagementApi()
          .deleteScheduleRecord(currentState.status.uuid, event.scheduleId);

      if (success) {
        final updatedSchedules = currentState.schedules
            .where((schedule) => schedule.scheduleId != event.scheduleId)
            .toList();

        emit(currentState.copyWith(schedules: updatedSchedules));
      } else {
        emit(currentState);
        // emit(const WaterHeaterFailedState(error: 'Failed to delete schedule.'));
      }
    }
  }

  FutureOr<void> _batchFetchWaterHeater(FetchWaterHeaterBatchStatusEvent event,
      Emitter<WaterHeaterState> emit) async {
    emit(WaterHeaterLoadingState());

    try {
      final status =
          await DevicesManagementApi().getBatchStatus(event.devicesUuid);
      deviceStatus = WaterHeaterStatusModel.fromJson(
          event.devicesUuid.first, status.status);

      emit(WaterHeaterDeviceStatusLoaded(deviceStatus));
    } catch (e) {
      emit(WaterHeaterDeviceStatusLoaded(deviceStatus));
    }
  }

  FutureOr<void> _batchControlWaterHeater(ControlWaterHeaterBatchEvent event,
      Emitter<WaterHeaterState> emit) async {
    if (state is WaterHeaterDeviceStatusLoaded) {
      final currentState = state as WaterHeaterDeviceStatusLoaded;

      final oldValue = _getValueByCode(event.code);

      _updateLocalValue(event.code, event.value);

      emit(currentState.copyWith(
        status: deviceStatus,
      ));

      final success = await _runDebounce(
        deviceId: event.devicesUuid,
        code: event.code,
        value: event.value,
        oldValue: oldValue,
        emit: emit,
        isBatch: true,
      );

      if (success) {
        if (event.code == "switch_1") {
          emit(currentState.copyWith(
            status: deviceStatus,
          ));
        }
      } else {
        _updateLocalValue(event.code, oldValue);
      }
    }
  }
}
