import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../../core/utils/cron_utils.dart';
import '../../../services/api_control_service.dart';
import '../../auth/auth_notifier.dart';
import '../../auth/auth_state.dart';
import 'control_state.dart';

// ── Provider ───────────────────────────────────────────────────
/// Provider global untuk [ControlNotifier] yang mengelola kendali pompa dan penjadwalan.
final controlProvider = NotifierProvider<ControlNotifier, ControlState>(ControlNotifier.new);

// ── Notifier ───────────────────────────────────────────────────
/// Notifier yang menangani logika kontrol hardware (ESP32) seperti menyalakan pompa,
/// mengubah mode kerja (Auto/Manual), batas sensor (threshold), serta tambah/hapus jadwal penyiraman.
class ControlNotifier extends Notifier<ControlState> {
  late ApiControlService _api;
  String _deviceId = '';

  /// Kunci sinkronisasi pompa (Sync Lock): mencegah status relay yang dilaporkan oleh polling berkala
  /// menimpa status tombol di UI (Optimistic Update) selama 10 detik pertama setelah user menekan tombol.
  DateTime? _syncLockedUntil;

  /// Kunci sinkronisasi mode (Sync Lock): mencegah status mode operasi yang dilaporkan oleh polling berkala
  /// menimpa pilihan mode di UI selama 10 detik pertama setelah user mengubah mode.
  DateTime? _modeSyncLockedUntil;

  @override
  ControlState build() {
    // Memantau perubahan status login pengguna untuk mengambil `deviceId`
    final authState = ref.watch(authProvider);
    if (authState is AuthSuccess && authState.user.deviceId != null) {
      _deviceId = authState.user.deviceId!;
    }
    
    // Inisialisasi ApiControlService dengan deviceId milik user
    _api = ApiControlService(_deviceId);

    // Memicu pemuatan data awal dari API secara asinkron setelah build widget selesai
    if (_deviceId.isNotEmpty) {
      Future.microtask(() => _initData());
    }

    return ControlState.initial();
  }

  /// Mengambil seluruh data konfigurasi awal (threshold, jadwal, mode operasi) dari API.
  Future<void> _initData() async {
    state = state.copyWith(isLoading: true);
    try {
      final thresholdTask = _api.getThreshold();
      final schedulesTask = _api.getSchedules();

      // Menjalankan request threshold dan jadwal secara paralel untuk efisiensi
      final results = await Future.wait([thresholdTask, schedulesTask]);
      final threshold = results[0] as Map<String, dynamic>;
      final schedulesData = results[1] as List<dynamic>;

      // Mengambil status mode operasi saat ini dari backend
      final currentMode = await _api.getMode();

      // Melakukan parsing data jadwal mentah dari backend ke objek WateringSchedule
      final parsedSchedules = schedulesData.map((s) {
        // Backend menyimpan format cron di zona waktu UTC: "minute hour day month day-of-week"
        // Misal: "30 10 * * 1,3,5" (Jam 10:30 UTC = Jam 17:30 WIB)
        final cronString = s['cron'].toString();
        final cronParts = cronString.split(' ');
        int utcMinute = 0;
        int utcHour = 0;
        if (cronParts.length >= 2) {
          utcMinute = int.tryParse(cronParts[0]) ?? 0;
          utcHour = int.tryParse(cronParts[1]) ?? 0;
        }

        // Konversi UTC ke zona waktu lokal WIB (+7 jam) agar jam yang tampil di UI cocok dengan input pengguna
        final utcTotalMinutes = utcHour * 60 + utcMinute;
        final wibTotalMinutes = (utcTotalMinutes + 7 * 60) % (24 * 60);
        final wibHour = wibTotalMinutes ~/ 60;
        final wibMinute = wibTotalMinutes % 60;

        // Mengekstrak daftar hari aktif dari string cron
        var daysArray = CronUtils.parseDaysFromCron(cronString);
        
        // JIKA konversi WIB (+7 jam) menyebabkan pergantian hari (ke esok hari),
        // maka daftar hari aktif harus digeser maju (+1 hari).
        if (utcTotalMinutes + 7 * 60 >= 24 * 60) {
          daysArray = daysArray.map((d) => (d + 1) % 7).toList();
        }

        final repeatText = CronUtils.getRepeatText(daysArray);

        return WateringSchedule(
          id: s['id'].toString(),
          time: TimeOfDay(hour: wibHour, minute: wibMinute),
          repeat: repeatText,
          isEnabled: s['is_active'] ?? true,
          cron: cronString,
          durationS: s['duration_s'] ?? 30,
        );
      }).toList();

      state = state.copyWith(
        isLoading: false,
        suhuMinLimit: (threshold['temp_min'] as num?)?.toDouble() ?? 22.0,
        suhuMaxLimit: (threshold['temp_max'] as num?)?.toDouble() ?? 30.0,
        kelembapanLimit: (threshold['hum_max'] as num?)?.toDouble() ?? 75.0,
        schedules: parsedSchedules,
        deviceMode: DeviceModeX.fromString(currentMode),
      );
    } catch (e) {
      state = state.copyWith(
        isLoading: false,
        errorMessage: 'Gagal memuat data awal. Periksa koneksi internet.',
      );
      debugPrint('Gagal _initData Control: $e');
    }
  }

  // ── Pesan Error & Success ──────────────────────────────────────
  /// Membersihkan pesan error dan sukses dari state saat ini.
  void clearMessage() {
    state = state.copyWith(clearError: true, clearSuccess: true);
  }

  // ── Pompa ────────────────────────────────────────────────────
  /// Mengaktifkan atau menonaktifkan status pompa secara langsung (Manual Control).
  /// Menerapkan Optimistic Update di mana UI langsung merespon sebelum request ke server selesai.
  Future<void> togglePump([int? durationSeconds]) async {
    final willTurnOn = !state.isPumpOn;
    
    // 1. Optimistic Update (UI berganti status secara instan)
    state = state.copyWith(
      isPumpOn: willTurnOn,
      activePumpDuration: willTurnOn ? (durationSeconds ?? 30) : null,
      pumpTurnedOnAt: willTurnOn ? DateTime.now() : null,
      clearTimer: !willTurnOn,
    );

    // 2. Kunci sync lock selama 10 detik agar polling tidak langsung menimpa status tombol
    _syncLockedUntil = DateTime.now().add(const Duration(seconds: 10));

    try {
      if (willTurnOn) {
        final duration = durationSeconds ?? 30; // Default penyiraman 30 detik
        await _api.siramManual(duration);
        state = state.copyWith(successMessage: 'Menyalakan pompa (${duration}s)');
      } else {
        await _api.stopPump();
        state = state.copyWith(successMessage: 'Pompa dimatikan');
      }
    } catch (e) {
      // 3. Batalkan/Revert status tombol jika request API mengalami kegagalan
      _syncLockedUntil = null;
      state = state.copyWith(isPumpOn: !willTurnOn, errorMessage: 'Gagal mengontrol pompa');
      debugPrint('Gagal toggle pompa: $e');
    }
  }

  /// Sinkronisasi status pompa dari data sensor real-time yang didapat dari ESP32.
  /// Dipanggil oleh `monitoringProvider` setiap 5 detik.
  /// CATATAN: Update akan diabaikan jika sync lock manual (10 detik) masih berjalan.
  void syncRelayFromSensor(bool relayOn) {
    if (_syncLockedUntil != null && DateTime.now().isBefore(_syncLockedUntil!)) {
      return; // Abaikan update sensor karena user baru saja memicu aksi manual
    }
    if (state.isPumpOn != relayOn) {
      state = state.copyWith(
        isPumpOn: relayOn,
        clearTimer: !relayOn,
      );
    }
  }

  /// Sinkronisasi status mode operasi dari data sensor real-time yang didapat dari ESP32.
  /// CATATAN: Update akan diabaikan jika sync lock mode (10 detik) masih aktif atau mode sedang loading.
  void syncModeFromSensor(String modeStr) {
    if (_modeSyncLockedUntil != null && DateTime.now().isBefore(_modeSyncLockedUntil!)) {
      return; // Abaikan update sensor karena user baru saja mengganti mode secara manual
    }

    final realMode = DeviceModeX.fromString(modeStr);
    
    if (state.deviceMode != realMode && !state.isModeLoading) {
      state = state.copyWith(deviceMode: realMode);
    }
  }

  /// Memperbarui status menyala pompa di state secara langsung.
  void setPump(bool value) => state = state.copyWith(isPumpOn: value);

  // ── Batasan Sensor ───────────────────────────────────────────
  /// Menyimpan batasan suhu min-maks ([suhuMin], [suhuMax]) dan kelembapan min ([kelembapan]) ke server.
  Future<void> saveThreshold(double suhuMin, double suhuMax, double kelembapan) async {
    final oldSuhuMin = state.suhuMinLimit;
    final oldSuhuMax = state.suhuMaxLimit;
    final oldKelembapan = state.kelembapanLimit;
    
    // Optimistic Update
    state = state.copyWith(
      suhuMinLimit: suhuMin,
      suhuMaxLimit: suhuMax,
      kelembapanLimit: kelembapan,
    );
    try {
      await _api.updateThreshold(suhuMin, suhuMax, kelembapan);
      state = state.copyWith(successMessage: 'Batasan sensor diperbarui');
    } catch (e) {
      // Revert ke nilai lama jika gagal menyimpan
      state = state.copyWith(
        suhuMinLimit: oldSuhuMin,
        suhuMaxLimit: oldSuhuMax,
        kelembapanLimit: oldKelembapan,
        errorMessage: 'Gagal menyimpan batasan sensor',
      );
      debugPrint('Gagal save threshold: $e');
    }
  }

  // ── Jadwal ───────────────────────────────────────────────────
  /// Menambahkan jadwal penyiraman otomatis baru.
  /// Melakukan konversi waktu lokal (WIB) ke format Cron UTC sebelum dikirim ke API.
  Future<void> addSchedule(TimeOfDay time, List<int> days, int durationS) async {
    state = state.copyWith(isLoading: true);
    try {
      // Konversi WIB (UTC+7) ke UTC dengan mengurangi waktu sebanyak 7 jam
      final localMinutes = time.hour * 60 + time.minute;
      final utcMinutes = (localMinutes - 7 * 60) % (24 * 60);
      final utcTotalMinutes = utcMinutes < 0 ? utcMinutes + 24 * 60 : utcMinutes;
      final utcHour = utcTotalMinutes ~/ 60;
      final utcMinute = utcTotalMinutes % 60;

      // JIKA pengurangan waktu WIB menyebabkan hari bergeser mundur (misal jam 05 WIB menjadi jam 22 UTC hari kemarin),
      // maka daftar hari aktif harus digeser mundur (-1 hari).
      List<int> adjustedDays = List.from(days);
      if (localMinutes - 7 * 60 < 0) {
        adjustedDays = days.map((d) => (d - 1) < 0 ? 6 : (d - 1)).toList();
      }
      final daysStr = CronUtils.daysToCron(adjustedDays);

      final cron = '$utcMinute $utcHour * * $daysStr';
      
      final result = await _api.addSchedule(cron, durationS, 'Jadwal Otomatis');
      
      final newSchedule = WateringSchedule(
        id: result['id'].toString(),
        time: time,
        repeat: CronUtils.getRepeatText(days),
        isEnabled: result['is_active'] ?? true,
        cron: result['cron']?.toString(),
        durationS: (result['duration_s'] as num?)?.toInt() ?? durationS,
      );

      state = state.copyWith(
        isLoading: false,
        schedules: [...state.schedules, newSchedule],
        successMessage: 'Jadwal baru ditambahkan',
      );
    } catch (e) {
      state = state.copyWith(isLoading: false, errorMessage: 'Gagal menambah jadwal');
      debugPrint('Gagal tambah schedule: $e');
    }
  }

  /// Mengaktifkan atau menonaktifkan jadwal penyiraman tertentu berdasarkan [id].
  Future<void> toggleSchedule(String id) async {
    final oldSchedules = state.schedules;
    
    // Optimistic Update
    state = state.copyWith(
      schedules: state.schedules.map((s) {
        return s.id == id ? s.copyWith(isEnabled: !s.isEnabled) : s;
      }).toList(),
    );

    try {
      await _api.toggleSchedule(id);
    } catch (e) {
      // Revert ke status jadwal semula jika request API gagal
      state = state.copyWith(schedules: oldSchedules, errorMessage: 'Gagal mengubah status jadwal');
      debugPrint('Gagal toggle schedule: $e');
    }
  }

  /// Menghapus jadwal penyiraman otomatis berdasarkan [id].
  Future<void> removeSchedule(String id) async {
    final oldSchedules = state.schedules;
    state = state.copyWith(
      schedules: state.schedules.where((s) => s.id != id).toList(),
      isLoading: true,
    );

    try {
      await _api.deleteSchedule(id);
      state = state.copyWith(isLoading: false, successMessage: 'Jadwal berhasil dihapus');
    } catch (e) {
      // Revert jika gagal menghapus
      state = state.copyWith(schedules: oldSchedules, isLoading: false, errorMessage: 'Gagal menghapus jadwal');
      debugPrint('Gagal hapus schedule: $e');
    }
  }

  // ── Mode Operasi ─────────────────────────────────────────────
  /// Mengganti mode kerja alat (Otomatis ke Manual, atau sebaliknya).
  Future<void> setDeviceMode(DeviceMode mode) async {
    if (state.isModeLoading) return;
    if (state.deviceMode == mode) return; // Mencegah request ganda untuk mode yang sama
    
    final prevMode = state.deviceMode;
    
    // Optimistic Update
    state = state.copyWith(deviceMode: mode, isModeLoading: true);

    // Kunci sync lock mode selama 10 detik agar status mode dari sensor tidak langsung menimpa UI sebelum hardware beres merespon
    _modeSyncLockedUntil = DateTime.now().add(const Duration(seconds: 10));

    try {
      await _api.setMode(mode.apiValue);
      state = state.copyWith(
        isModeLoading: false,
        successMessage: 'Mode diubah ke ${mode.label}',
      );
    } catch (e) {
      // Revert mode jika gagal menyimpan ke server
      _modeSyncLockedUntil = null;
      state = state.copyWith(
        deviceMode: prevMode,
        isModeLoading: false,
        errorMessage: 'Gagal mengubah mode operasi',
      );
    }
  }
}
