import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.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';

class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
  final String deviceId;
  late GarageDoorStatusModel deviceStatus;
  Timer? _timer;

  GarageDoorBloc({required this.deviceId}) : super(GarageDoorInitialState()) {
    on<GarageDoorInitialEvent>(_fetchGarageDoorStatus);
    on<GarageDoorControlEvent>(_garageDoorControlEvent);
    on<AddGarageDoorScheduleEvent>(_addSchedule);
    on<UpdateGarageDoorScheduleEvent>(_updateSchedule);
    on<DeleteGarageDoorScheduleEvent>(_deleteSchedule);
    on<FetchGarageDoorSchedulesEvent>(_fetchSchedules);
    on<IncreaseGarageDoorDelayEvent>(_increaseDelay);
    on<DecreaseGarageDoorDelayEvent>(_decreaseDelay);
    on<FetchGarageDoorRecordsEvent>(_fetchRecords);
    on<GarageDoorUpdatedEvent>(_handleUpdate);
    on<UpdateSelectedTimeEvent>(_updateSelectedTime);
    on<UpdateSelectedDayEvent>(_updateSelectedDay);
    on<UpdateFunctionOnEvent>(_updateFunctionOn);
    on<InitializeAddScheduleEvent>(_initializeAddSchedule);
    on<BackToGarageDoorGridViewEvent>(_backToGridView);
    on<UpdateCountdownAlarmEvent>(_onUpdateCountdownAlarm);
    on<UpdateTrTimeConEvent>(_onUpdateTrTimeCon);
    on<GarageDoorBatchControlEvent>(_onBatchControl);
    on<GarageDoorFetchBatchStatusEvent>(_onFetchBatchStatus);
    on<GarageDoorFactoryResetEvent>(_onFactoryReset);
    on<EditGarageDoorScheduleEvent>(_onEditSchedule);
    on<StatusUpdated>(_onStatusUpdated);
  }
  _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 =
            GarageDoorStatusModel.fromJson(usersMap['productUuid'], statusList);
        if (!isClosed) {
          add(StatusUpdated(deviceStatus));
        }
      });
    } catch (_) {}
  }

  void _onStatusUpdated(StatusUpdated event, Emitter<GarageDoorState> emit) {
    deviceStatus = event.deviceStatus;
    emit(GarageDoorLoadedState(status: deviceStatus));
  }

  void _fetchGarageDoorStatus(
      GarageDoorInitialEvent event, Emitter<GarageDoorState> emit) async {
    emit(GarageDoorLoadingState());
    try {
      var response =
          await DevicesManagementApi().getDeviceStatus(event.deviceId);
      deviceStatus = GarageDoorStatusModel.fromJson(deviceId, response.status);
      _listenToChanges(deviceId);
      emit(GarageDoorLoadedState(status: deviceStatus));
    } catch (e) {
      emit(GarageDoorErrorState(message: e.toString()));
    }
  }

  Future<void> _onFetchBatchStatus(GarageDoorFetchBatchStatusEvent event,
      Emitter<GarageDoorState> emit) async {
    emit(GarageDoorLoadingState());
    try {
      final status =
          await DevicesManagementApi().getBatchStatus(event.deviceIds);
      deviceStatus =
          GarageDoorStatusModel.fromJson(event.deviceIds.first, status.status);
      emit(GarageDoorBatchStatusLoaded(deviceStatus));
    } catch (e) {
      emit(GarageDoorBatchControlError(e.toString()));
    }
  }

  Future<void> _addSchedule(
      AddGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
    try {
      ScheduleEntry newSchedule = ScheduleEntry(
        category: event.category,
        time: formatTimeOfDayToISO(event.time),
        function: Status(code: 'doorcontact_state', value: event.functionOn),
        days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
      );
      bool success =
          await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId);
      if (success) {
        add(FetchGarageDoorSchedulesEvent(
            deviceId: deviceId, category: 'doorcontact_state'));
      } else {
        emit(GarageDoorLoadedState(status: deviceStatus));
      }
    } catch (e) {
      emit(GarageDoorLoadedState(status: deviceStatus));
    }
  }

  void _onUpdateCountdownAlarm(
      UpdateCountdownAlarmEvent event, Emitter<GarageDoorState> emit) {
    final currentState = state;
    if (currentState is GarageDoorLoadedState) {
      emit(currentState.copyWith(
        status:
            currentState.status.copyWith(countdownAlarm: event.countdownAlarm),
      ));
    }
  }

  void _onUpdateTrTimeCon(
      UpdateTrTimeConEvent event, Emitter<GarageDoorState> emit) {
    final currentState = state;
    if (currentState is GarageDoorLoadedState) {
      emit(currentState.copyWith(
        status: currentState.status.copyWith(trTimeCon: event.trTimeCon),
      ));
    }
  }

  Future<void> _updateSchedule(UpdateGarageDoorScheduleEvent event,
      Emitter<GarageDoorState> emit) async {
    try {
      final updatedSchedules = deviceStatus.schedules?.map((schedule) {
        if (schedule.scheduleId == event.scheduleId) {
          return schedule.copyWith(
            function:
                Status(code: 'doorcontact_state', value: event.functionOn),
            enable: event.enable,
          );
        }
        return schedule;
      }).toList();
      bool success = await DevicesManagementApi().updateScheduleRecord(
        enable: event.enable,
        uuid: deviceStatus.uuid,
        scheduleId: event.scheduleId,
      );
      if (success) {
        deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules);
        emit(GarageDoorLoadedState(status: deviceStatus));
      } else {
        emit(GarageDoorLoadedState(status: deviceStatus));
      }
    } catch (e) {
      emit(GarageDoorLoadedState(status: deviceStatus));
    }
  }

  Future<void> _deleteSchedule(DeleteGarageDoorScheduleEvent event,
      Emitter<GarageDoorState> emit) async {
    try {
      bool success = await DevicesManagementApi()
          .deleteScheduleRecord(deviceStatus.uuid, event.scheduleId);
      if (success) {
        final updatedSchedules = deviceStatus.schedules
            ?.where((schedule) => schedule.scheduleId != event.scheduleId)
            .toList();
        deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules);
        emit(GarageDoorLoadedState(status: deviceStatus));
      } else {
        emit(GarageDoorLoadedState(status: deviceStatus));
      }
    } catch (e) {
      emit(GarageDoorLoadedState(status: deviceStatus));
    }
  }

  Future<void> _fetchSchedules(FetchGarageDoorSchedulesEvent event,
      Emitter<GarageDoorState> emit) async {
    emit(ScheduleGarageLoadingState());
    try {
      List<ScheduleModel> schedules = await DevicesManagementApi()
          .getDeviceSchedules(deviceStatus.uuid, event.category);
      deviceStatus = deviceStatus.copyWith(schedules: schedules);
      emit(
        GarageDoorLoadedState(
          status: deviceStatus,
          scheduleMode: ScheduleModes.schedule,
        ),
      );
    } catch (e) {
      emit(
        GarageDoorLoadedState(
          status: deviceStatus,
          scheduleMode: ScheduleModes.schedule,
        ),
      );
    }
  }

  Future<void> _updateSelectedTime(
      UpdateSelectedTimeEvent event, Emitter<GarageDoorState> emit) async {
    final currentState = state;
    if (currentState is GarageDoorLoadedState) {
      emit(currentState.copyWith(selectedTime: event.selectedTime));
    }
  }

  Future<void> _updateSelectedDay(
      UpdateSelectedDayEvent event, Emitter<GarageDoorState> emit) async {
    final currentState = state;
    if (currentState is GarageDoorLoadedState) {
      List<bool> updatedDays = List.from(currentState.selectedDays);
      updatedDays[event.dayIndex] = event.isSelected;
      emit(currentState.copyWith(
          selectedDays: updatedDays, selectedTime: currentState.selectedTime));
    }
  }

  Future<void> _updateFunctionOn(
      UpdateFunctionOnEvent event, Emitter<GarageDoorState> emit) async {
    final currentState = state;
    if (currentState is GarageDoorLoadedState) {
      emit(currentState.copyWith(
          functionOn: event.functionOn,
          selectedTime: currentState.selectedTime));
    }
  }

  Future<void> _initializeAddSchedule(
      InitializeAddScheduleEvent event, Emitter<GarageDoorState> emit) async {
    final currentState = state;
    if (currentState is GarageDoorLoadedState) {
      emit(currentState.copyWith(
        selectedTime: event.selectedTime,
        selectedDays: event.selectedDays,
        functionOn: event.functionOn,
        isEditing: event.isEditing,
      ));
    }
  }

  Future<void> _fetchRecords(
      FetchGarageDoorRecordsEvent event, Emitter<GarageDoorState> emit) async {
    emit(GarageDoorReportsLoadingState());
    try {
      final from = DateTime.now()
          .subtract(const Duration(days: 30))
          .millisecondsSinceEpoch;
      final to = DateTime.now().millisecondsSinceEpoch;
      final DeviceReport records =
          await DevicesManagementApi.getDeviceReportsByDate(
              event.deviceId, 'switch_1', from.toString(), to.toString());
      emit(GarageDoorReportsState(deviceReport: records));
    } catch (e) {
      emit(GarageDoorReportsFailedState(error: e.toString()));
    }
  }

  Future<void> _onBatchControl(
      GarageDoorBatchControlEvent event, Emitter<GarageDoorState> emit) async {
    final oldValue =
        event.code == 'doorcontact_state' ? deviceStatus.switch1 : false;

    _updateLocalValue(event.code, event.value);
    emit(GarageDoorBatchStatusLoaded(deviceStatus));

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

    if (!success) {
      _revertValue(event.code, oldValue, emit);
    }
  }

  void _backToGridView(
      BackToGarageDoorGridViewEvent event, Emitter<GarageDoorState> emit) {
    emit(GarageDoorLoadedState(status: deviceStatus));
  }

  void _handleUpdate(
      GarageDoorUpdatedEvent event, Emitter<GarageDoorState> emit) {
    emit(GarageDoorLoadedState(status: deviceStatus));
  }

  Future<bool> _runDeBouncer({
    required dynamic deviceId,
    required String code,
    required dynamic value,
    required dynamic oldValue,
    required Emitter<GarageDoorState> 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);
        return false;
      } else {
        return true;
      }
    } catch (e) {
      _revertValue(code, oldValue, emit);
      return false;
    }
  }

  Future<void> _onFactoryReset(
      GarageDoorFactoryResetEvent event, Emitter<GarageDoorState> emit) async {
    emit(GarageDoorLoadingState());
    try {
      final response = await DevicesManagementApi()
          .factoryReset(event.factoryReset, event.deviceId);
      if (!response) {
        emit(const GarageDoorErrorState(message: 'Failed to reset device'));
      } else {
        emit(GarageDoorLoadedState(status: deviceStatus));
      }
    } catch (e) {
      emit(GarageDoorErrorState(message: e.toString()));
    }
  }

  void _increaseDelay(
      IncreaseGarageDoorDelayEvent event, Emitter<GarageDoorState> emit) async {
    // if (deviceStatus.countdown1 != 0) {
    try {
      deviceStatus = deviceStatus.copyWith(
          delay: deviceStatus.delay + Duration(minutes: 10));
      emit(GarageDoorLoadedState(status: deviceStatus));
      add(GarageDoorControlEvent(
          deviceId: deviceId,
          value: deviceStatus.delay.inSeconds,
          code: 'countdown_1'));
    } catch (e) {
      emit(GarageDoorErrorState(message: e.toString()));
    }
    // }
  }

  void _decreaseDelay(
      DecreaseGarageDoorDelayEvent event, Emitter<GarageDoorState> emit) async {
    // if (deviceStatus.countdown1 != 0) {
    try {
      if (deviceStatus.delay.inMinutes > 10) {
        deviceStatus = deviceStatus.copyWith(
            delay: deviceStatus.delay - Duration(minutes: 10));
      }
      emit(GarageDoorLoadedState(status: deviceStatus));
      add(GarageDoorControlEvent(
          deviceId: deviceId,
          value: deviceStatus.delay.inSeconds,
          code: 'countdown_1'));
    } catch (e) {
      emit(GarageDoorErrorState(message: e.toString()));
    }
    //}
  }

  void _garageDoorControlEvent(
      GarageDoorControlEvent event, Emitter<GarageDoorState> emit) async {
    final oldValue = event.code == 'countdown_1'
        ? deviceStatus.countdown1
        : deviceStatus.switch1;
    _updateLocalValue(event.code, event.value);
    emit(GarageDoorLoadedState(status: deviceStatus));
    final success = await _runDeBouncer(
      deviceId: deviceId,
      code: event.code,
      value: event.value,
      oldValue: oldValue,
      emit: emit,
      isBatch: false,
    );
    if (!success) {
      _revertValue(event.code, oldValue, emit);
    }
  }

  void _revertValue(
      String code, dynamic oldValue, Emitter<GarageDoorState> emit) {
    switch (code) {
      case 'doorcontact_state':
        if (oldValue is bool) {
          deviceStatus = deviceStatus.copyWith(switch1: oldValue);
        }
        break;
      case 'countdown_1':
        if (oldValue is int) {
          deviceStatus = deviceStatus.copyWith(
              countdown1: oldValue, delay: Duration(seconds: oldValue));
        }
        break;
      // Add other cases if needed
      default:
        break;
    }
    if (state is GarageDoorLoadedState) {
      final currentState = state as GarageDoorLoadedState;
      emit(currentState.copyWith(status: deviceStatus));
    }
  }

  void _updateLocalValue(String code, dynamic value) {
    switch (code) {
      case 'switch_1':
        if (value is bool) {
          deviceStatus = deviceStatus.copyWith(switch1: value);
        }
        break;
      case 'countdown_1':
        if (value is int) {
          deviceStatus = deviceStatus.copyWith(
              countdown1: value, delay: Duration(seconds: value));
        }
        break;
      case 'countdown_alarm':
        if (value is int) {
          deviceStatus = deviceStatus.copyWith(countdownAlarm: value);
        }
        break;
      case 'tr_timecon':
        if (value is int) {
          deviceStatus = deviceStatus.copyWith(trTimeCon: value);
        }
        break;
      case 'door_state_1':
        if (value is String) {
          deviceStatus = deviceStatus.copyWith(doorState1: value);
        }
        break;
      case 'door_control_1':
        if (value is String) {
          deviceStatus = deviceStatus.copyWith(doorControl1: value);
        }
        break;
      case 'voice_control_1':
        if (value is bool) {
          deviceStatus = deviceStatus.copyWith(voiceControl1: value);
        }
        break;
      case 'doorcontact_state':
        if (value is bool) {
          deviceStatus = deviceStatus.copyWith(doorContactState: value);
        }

      default:
        break;
    }
  }

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

  FutureOr<void> _onEditSchedule(
      EditGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
    try {
      ScheduleEntry newSchedule = ScheduleEntry(
        scheduleId: event.scheduleId,
        category: event.category,
        time: formatTimeOfDayToISO(event.time),
        function: Status(code: 'doorcontact_state', value: event.functionOn),
        days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
      );
      bool success = await DevicesManagementApi()
          .editScheduleRecord(deviceId, newSchedule);
      if (success) {
        add(FetchGarageDoorSchedulesEvent(
            deviceId: deviceId, category: 'doorcontact_state'));
      } else {
        emit(GarageDoorLoadedState(status: deviceStatus));
      }
    } catch (e) {
      emit(GarageDoorLoadedState(status: deviceStatus));
    }
  }
}
