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/batch_control_devices_service.dart';
import 'package:syncrow_web/services/control_device_service.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> {
  late WaterHeaterStatusModel deviceStatus;
  final String deviceId;
  final ControlDeviceService controlDeviceService;
  final BatchControlDevicesService batchControlDevicesService;
  Timer? _countdownTimer;

  WaterHeaterBloc({
    required this.deviceId,
    required this.controlDeviceService,
    required this.batchControlDevicesService,
  }) : 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);
  }

  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,
      ));
    }
  }

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

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

  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));
  }

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

  Future<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,
          ),
        );
      }
    }
  }

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

      _updateLocalValue(event.code, event.value);

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

      final success = await controlDeviceService.controlDevice(
        deviceUuid: event.deviceId,
        status: Status(
          code: event.code,
          value: event.value,
        ),
      );

      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);
          emit(
            currentState.copyWith(
              inchingHours: inchingDuration.inHours,
              inchingMinutes: inchingDuration.inMinutes % 60,
              isInchingActive: true,
            ),
          );
        }
      }
    }
  }

  Future<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()));
      }
    }
  }

  Future<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,
          ));
        } 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()));
    }
  }

  void _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 _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;
        }

        final totalSeconds = newRemaining.inSeconds;
        final newHours = totalSeconds ~/ 3600;
        final newMinutes = (totalSeconds % 3600) ~/ 60;

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

  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) {
    return switch (code) {
      'switch_1' => deviceStatus.heaterSwitch,
      'countdown_1' =>
        (deviceStatus.countdownHours * 60) + deviceStatus.countdownMinutes,
      _ => null,
    };
  }

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

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

    try {
      final schedules = await DevicesManagementApi().getDeviceSchedules(
        deviceStatus.uuid,
        event.category,
      );

      emit(WaterHeaterDeviceStatusLoaded(
        deviceStatus,
        schedules: schedules,
        scheduleMode: ScheduleModes.schedule,
      ));
    } catch (e) {
      emit(WaterHeaterDeviceStatusLoaded(
        deviceStatus,
        schedules: const [],
      ));
    }
  }

  Future<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),
      );

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

      if (success) {
        add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid));
      } else {
        emit(currentState);
      }
    }
  }

  Future<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),
      );

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

      if (success) {
        add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid));
      } else {
        emit(currentState);
      }
    }
  }

  Future<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);
      }
    }
  }

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

      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);
      }
    }
  }

  Future<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));
    }
  }

  Future<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 batchControlDevicesService.batchControlDevices(
        uuids: event.devicesUuid,
        code: event.code,
        value: event.value,
      );

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