import 'dart:async';

import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../../models/chart_data.dart';
import '../../../models/monitoring_status.dart';
import '../../../models/sensor_model.dart';
import '../../../repositories/iot_repository.dart';
import '../../../services/api_iot_service.dart';
import '../../auth/auth_notifier.dart';
import '../../auth/auth_state.dart';
import 'control_notifier.dart';

// ── Enums ──────────────────────────────────────────────────────
/// Rentang waktu grafik: [daily] untuk harian, [monthly] untuk bulanan.
enum ChartRange { daily, monthly }

/// Tipe data yang ditampilkan di grafik: [suhu] (temperatur) atau [kelembapan] (humidity).
enum ChartType  { suhu, kelembapan }

// ── Supporting providers ───────────────────────────────────────
/// Provider untuk menyediakan instance [IotRepository].
/// Secara dinamis mengambil `deviceId` dari pengguna yang sedang login ([authProvider]).
/// Jika pengguna belum login atau tidak memiliki `deviceId`, akan menggunakan fallback default 'esp32-01'.
final iotRepositoryProvider = Provider<IotRepository>((ref) {
  final authState = ref.watch(authProvider);
  String deviceId = 'esp32-01'; // Default fallback jika deviceId tidak ditemukan
  
  if (authState is AuthSuccess && authState.user.deviceId != null) {
    deviceId = authState.user.deviceId!;
  }
  
  return IotRepository(ApiIotService(deviceId));
});

/// Provider untuk menyimpan status pilihan rentang waktu grafik (Harian / Bulanan).
final chartRangeProvider = StateProvider<ChartRange>(
  (_) => ChartRange.daily,
);

/// Provider untuk menyimpan status pilihan tipe grafik yang aktif (Suhu / Kelembapan).
final chartTypeProvider = StateProvider<ChartType>(
  (_) => ChartType.suhu,
);

/// Provider untuk menyimpan status offset grafik (untuk navigasi hari/bulan sebelumnya atau setelahnya).
final chartOffsetProvider = StateProvider<int>(
  (_) => 0,
);

// ── State ──────────────────────────────────────────────────────
/// State yang merepresentasikan semua data pemantauan IoT di UI.
class MonitoringState {
  /// Data sensor terbaru yang diterima dari ESP32.
  final SensorModel? latest;
  
  /// Status loading saat aplikasi pertama kali memuat data atau memperbarui secara manual.
  final bool isLoading;
  
  /// Pesan error jika terjadi kegagalan pemanggilan API.
  final String? errorMessage;
  
  /// Riwayat data sensor (real-time) untuk grafik harian berjalan (maksimal 200 poin).
  final List<ChartData> history;
  
  /// Agregasi riwayat harian rata-rata untuk grafik bulanan.
  final List<DailyAverage> dailyHistory;
  
  /// Agregasi riwayat per jam rata-rata untuk grafik harian.
  final List<DailyAverage> hourlyHistory;

  /// Status keaktifan perangkat berdasarkan LWT MQTT di database.
  final bool isDeviceOnline;

  const MonitoringState({
    this.latest,
    this.isLoading = false,
    this.errorMessage,
    this.history = const [],
    this.dailyHistory = const [],
    this.hourlyHistory = const [],
    this.isDeviceOnline = false,
  });

  /// State awal saat provider pertama kali diinisialisasi (status loading aktif).
  factory MonitoringState.initial() => const MonitoringState(isLoading: true);

  /// Helper getter untuk mendapatkan nilai suhu saat ini. Default: 0 jika null.
  double get temperature => latest?.temperature ?? 0;
  
  /// Helper getter untuk mendapatkan nilai kelembapan saat ini. Default: 0 jika null.
  double get humidity    => latest?.humidity    ?? 0;

  /// Menentukan status suhu berdasarkan batas minimum ([minSuhu]) dan maksimum ([maxSuhu]).
  /// - Danger: jika suhu melebihi batas maks + 3°C atau di bawah batas min - 3°C.
  /// - Warning: jika suhu melebihi batas maks atau di bawah batas min.
  /// - Normal: jika suhu berada di dalam rentang.
  MonitoringStatus tempStatus(double minSuhu, double maxSuhu) {
    if (temperature >= maxSuhu + 3 || temperature <= minSuhu - 3) return MonitoringStatus.danger;
    if (temperature > maxSuhu || temperature < minSuhu)  return MonitoringStatus.warning;
    return MonitoringStatus.normal;
  }

  /// Menentukan status kelembapan berdasarkan batas minimum kelembapan ([minHum]).
  /// - Danger: jika kelembapan berada di bawah batas min - 5%.
  /// - Warning: jika kelembapan berada di bawah batas min.
  /// - Normal: jika kelembapan berada di atas batas min.
  MonitoringStatus humidStatus(double minHum) {
    if (humidity < minHum - 5) return MonitoringStatus.danger;
    if (humidity < minHum) return MonitoringStatus.warning;
    return MonitoringStatus.normal;
  }

  /// Menentukan status keseluruhan kumbung jamur berdasarkan status suhu dan kelembapan.
  /// Menghasilkan string 'Danger', 'Warning', atau 'Normal'.
  String overallStatus(double minSuhu, double maxSuhu, double minHum) {
    final statuses = [tempStatus(minSuhu, maxSuhu), humidStatus(minHum)];
    if (statuses.contains(MonitoringStatus.danger))  return 'Danger';
    if (statuses.contains(MonitoringStatus.warning)) return 'Warning';
    return 'Normal';
  }

  /// Menggandakan [MonitoringState] dengan memperbarui beberapa parameter saja (Immutable State).
  MonitoringState copyWith({
    Object? latest = _sentinel,
    bool? isLoading,
    String? errorMessage,
    List<ChartData>? history,
    List<DailyAverage>? dailyHistory,
    List<DailyAverage>? hourlyHistory,
    bool? isDeviceOnline,
  }) {
    return MonitoringState(
      latest: latest == _sentinel ? this.latest : latest as SensorModel?,
      isLoading: isLoading ?? this.isLoading,
      errorMessage: errorMessage,   // null = menghapus pesan error
      history: history ?? this.history,
      dailyHistory: dailyHistory ?? this.dailyHistory,
      hourlyHistory: hourlyHistory ?? this.hourlyHistory,
      isDeviceOnline: isDeviceOnline ?? this.isDeviceOnline,
    );
  }
}

// Sentinel object untuk membedakan "tidak diisi" vs "diisi null" di copyWith
const _sentinel = Object();

// ── Notifier ───────────────────────────────────────────────────
/// Notifier yang mengelola logika bisnis pemantauan sensor real-time.
/// Menghubungi repository secara berkala untuk mengambil data sensor dan riwayatnya.
class MonitoringNotifier extends StateNotifier<MonitoringState> {
  final IotRepository _repo;
  final Ref _ref;
  Timer? _timer;

  MonitoringNotifier(this._repo, this._ref) : super(MonitoringState.initial()) {
    _startAutoRefresh();
  }

  /// Memulai auto-refresh berkala (polling) setiap 5 detik setelah memuat data awal.
  Future<void> _startAutoRefresh() async {
    await _initData();
    _timer = Timer.periodic(const Duration(seconds: 5), (_) => fetchData());
  }

  /// Memuat data sensor awal beserta data riwayat grafik dari API.
  Future<void> _initData() async {
    final authState = _ref.read(authProvider);
    final hasDevice = authState is AuthSuccess && authState.user.deviceId != null;
    if (!hasDevice) {
      // Jika pengguna belum terhubung dengan alat, loading dimatikan
      state = state.copyWith(isLoading: false);
      return;
    }

    state = state.copyWith(isLoading: true);
    try {
      // 1. Ambil data riwayat sensor mentah dari backend
      final historyData = await _repo.getHistoryData();
      final latest = historyData.isNotEmpty ? historyData.last : null;
      
      // Konversi riwayat sensor mentah ke format ChartData untuk grafik
      final historyCharts = historyData.map((s) => ChartData(
        timestamp: s.timestamp,
        suhu: s.temperature,
        kelembapan: s.humidity,
      )).toList();

      List<DailyAverage> dailyParsed = [];
      List<DailyAverage> hourlyParsed = [];
      try {
        // 2. Ambil riwayat agregasi harian (30 hari terakhir) untuk grafik bulanan
        final dailyRaw = await _repo.getDailyHistory(days: 30);
        dailyParsed = dailyRaw.map((map) {
          final String dateStr = map['date'] ?? map['log_date'] ?? map['created_at'] ?? '';
          final DateTime date = (DateTime.tryParse(dateStr) ?? DateTime.now()).toLocal();
          final num? t = map['avg_temp'] ?? map['avg_temperature'] ?? map['temperature'];
          final num? h = map['avg_hum'] ?? map['avg_humidity'] ?? map['humidity'];
          return DailyAverage(date: date, suhu: t?.toDouble(), kelembapan: h?.toDouble());
        }).toList();

        // 3. Ambil riwayat agregasi per jam (7 hari terakhir) untuk grafik harian
        final hourlyRaw = await _repo.getHourlyHistory(days: 7);
        hourlyParsed = hourlyRaw.map((map) {
          final String dateStr = map['date'] ?? map['log_date'] ?? map['created_at'] ?? '';
          final DateTime date = (DateTime.tryParse(dateStr) ?? DateTime.now()).toLocal();
          final num? t = map['avg_temp'] ?? map['avg_temperature'] ?? map['temperature'];
          final num? h = map['avg_hum'] ?? map['avg_humidity'] ?? map['humidity'];
          return DailyAverage(date: date, suhu: t?.toDouble(), kelembapan: h?.toDouble());
        }).toList();
      } catch (e) {
        // Abaikan jika endpoint agregasi belum tersedia atau terjadi error (chart akan kosong)
      }

      // Ambil status keaktifan perangkat dari database
      bool isDeviceOnline = false;
      try {
        final deviceStatus = await _repo.getDeviceStatus();
        isDeviceOnline = deviceStatus['is_online'] as bool? ?? false;
      } catch (e) {
        if (latest != null) {
          final diff = DateTime.now().difference(latest.timestamp);
          isDeviceOnline = diff.inMinutes < 5;
        }
      }

      state = state.copyWith(
        latest: latest,
        history: historyCharts,
        dailyHistory: dailyParsed,
        hourlyHistory: hourlyParsed,
        isDeviceOnline: isDeviceOnline,
        isLoading: false,
        errorMessage: null,
      );
    } catch (e) {
      state = state.copyWith(isLoading: false, errorMessage: e.toString());
    }
  }

  /// Mengambil data sensor terbaru dari backend secara real-time.
  /// Dipanggil oleh [Timer] setiap 5 detik sekali.
  Future<void> fetchData({bool showLoading = false}) async {
    final authState = _ref.read(authProvider);
    final hasDevice = authState is AuthSuccess && authState.user.deviceId != null;
    if (!hasDevice) return;

    if (showLoading) {
      state = state.copyWith(isLoading: true);
    }

    try {
      final sensor = await _repo.getSensorData();
      
      // Verifikasi apakah data yang masuk benar-benar baru berdasarkan timestamp-nya
      bool isNew = true;
      if (state.history.isNotEmpty) {
        final lastPoint = state.history.last;
        if (!sensor.timestamp.isAfter(lastPoint.timestamp)) {
          isNew = false;
        }
      }

      List<ChartData> updatedHistory = List.from(state.history);
      
      if (isNew) {
        final newPoint = ChartData(
          timestamp: sensor.timestamp,
          suhu: sensor.temperature,
          kelembapan: sensor.humidity,
        );
        updatedHistory.add(newPoint);
      }

      // Membatasi panjang riwayat real-time maksimal 200 data poin agar hemat RAM
      final trimmed = updatedHistory.length > 200
          ? updatedHistory.sublist(updatedHistory.length - 200)
          : updatedHistory;

      // Ambil status keaktifan perangkat dari database
      bool isDeviceOnline = false;
      try {
        final deviceStatus = await _repo.getDeviceStatus();
        isDeviceOnline = deviceStatus['is_online'] as bool? ?? false;
      } catch (e) {
        final diff = DateTime.now().difference(sensor.timestamp);
        isDeviceOnline = diff.inMinutes < 5;
      }

      state = state.copyWith(
        latest: sensor,
        isLoading: false,
        history: trimmed,
        isDeviceOnline: isDeviceOnline,
        errorMessage: null,
      );

      // Sinkronisasi status pompa dan mode yang didapat dari payload data sensor ESP32
      // Ini memastikan UI sinkron dengan hardware asli jika dikendalikan secara eksternal
      try {
        _ref.read(controlProvider.notifier).syncRelayFromSensor(sensor.relayOn);
        _ref.read(controlProvider.notifier).syncModeFromSensor(sensor.mode);
      } catch (_) {
        // Abaikan jika controlProvider belum siap (misal saat startup)
      }
    } catch (e) {
      state = state.copyWith(
        isLoading: false,
        errorMessage: e.toString(),
      );
    }
  }

  /// Memuat riwayat data agregasi harian/bulanan dengan offset tertentu (navigasi grafik).
  Future<void> fetchHistoryForOffset({
    required ChartRange range,
    required int offset,
  }) async {
    try {
      if (range == ChartRange.daily) {
        final int targetDays = offset.abs() + 1;
        final int fetchDays = targetDays < 7 ? 7 : targetDays;
        
        final hourlyRaw = await _repo.getHourlyHistory(days: fetchDays);
        final hourlyParsed = hourlyRaw.map((map) {
          final String dateStr = map['date'] ?? map['log_date'] ?? map['created_at'] ?? '';
          final DateTime date = (DateTime.tryParse(dateStr) ?? DateTime.now()).toLocal();
          final num? t = map['avg_temp'] ?? map['avg_temperature'] ?? map['temperature'];
          final num? h = map['avg_hum'] ?? map['avg_humidity'] ?? map['humidity'];
          return DailyAverage(date: date, suhu: t?.toDouble(), kelembapan: h?.toDouble());
        }).toList();

        state = state.copyWith(
          hourlyHistory: hourlyParsed,
          errorMessage: null,
        );
      } else {
        final int targetDays = (offset.abs() + 1) * 31;
        final int fetchDays = targetDays < 30 ? 30 : targetDays;

        final dailyRaw = await _repo.getDailyHistory(days: fetchDays);
        final dailyParsed = dailyRaw.map((map) {
          final String dateStr = map['date'] ?? map['log_date'] ?? map['created_at'] ?? '';
          final DateTime date = (DateTime.tryParse(dateStr) ?? DateTime.now()).toLocal();
          final num? t = map['avg_temp'] ?? map['avg_temperature'] ?? map['temperature'];
          final num? h = map['avg_hum'] ?? map['avg_humidity'] ?? map['humidity'];
          return DailyAverage(date: date, suhu: t?.toDouble(), kelembapan: h?.toDouble());
        }).toList();

        state = state.copyWith(
          dailyHistory: dailyParsed,
          errorMessage: null,
        );
      }
    } catch (e) {
      // Abaikan agar tidak memutus polling data sensor real-time
    }
  }

  /// Ambil data riwayat yang sudah difilter berdasarkan rentang waktu hari ini/bulan ini.
  List<ChartData> getFilteredData(ChartRange range) {
    final now = DateTime.now();

    if (range == ChartRange.daily) {
      final startOfDay = DateTime(now.year, now.month, now.day);
      final endOfDay = startOfDay.add(const Duration(days: 1));
      return state.history.where((d) =>
        !d.timestamp.isBefore(startOfDay) &&
        d.timestamp.isBefore(endOfDay),
      ).toList();
    } else {
      final firstDay = DateTime(now.year, now.month, 1);
      final firstNext = DateTime(now.year, now.month + 1, 1);
      return state.history.where((d) =>
        d.timestamp.isAfter(firstDay.subtract(const Duration(seconds: 1))) &&
        d.timestamp.isBefore(firstNext),
      ).toList();
    }
  }

  /// Menghentikan polling berkala.
  void stopRefresh() => _timer?.cancel();

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }
}

// ── Main provider ──────────────────────────────────────────────
/// Provider utama untuk memantau data sensor. Digunakan oleh UI halaman Monitoring.
final monitoringProvider =
    StateNotifierProvider<MonitoringNotifier, MonitoringState>(
  (ref) => MonitoringNotifier(ref.watch(iotRepositoryProvider), ref),
);

// ── Daily average provider ─────────────────────────────────────
/// Model penampung nilai rata-rata suhu & kelembapan pada jam/hari tertentu.
class DailyAverage {
  final DateTime date;
  final double? suhu;
  final double? kelembapan;

  const DailyAverage({required this.date, this.suhu, this.kelembapan});
}

/// Provider yang memproses & mencocokkan riwayat data sensor dengan kalender lokal.
/// Menghasilkan daftar koordinat titik grafik yang siap digambar oleh FL Chart.
final dailyAverageProvider = Provider<List<DailyAverage>>((ref) {
  final range   = ref.watch(chartRangeProvider);
  final monitoring = ref.watch(monitoringProvider);
  final offset  = ref.watch(chartOffsetProvider);
  final now     = DateTime.now();

  final int totalDays;
  final DateTime Function(int) dateAt;

  if (range == ChartRange.daily) {
    // Grafik Harian: 24 titik grafik (jam 00 s.d 23)
    totalDays = 24;
    final targetDate = now.add(Duration(days: offset)); // offset = 0 adalah hari ini, -1 kemarin, dst.
    dateAt = (i) => DateTime(targetDate.year, targetDate.month, targetDate.day, i);
  } else {
    // Grafik Bulanan: Titik sesuai jumlah hari dalam bulan terpilih (28 s.d 31)
    final targetMonth = DateTime(now.year, now.month + offset, 1);
    final firstNext = DateTime(targetMonth.year, targetMonth.month + 1, 1);
    final lastDay   = firstNext.subtract(const Duration(days: 1));
    totalDays = lastDay.day;
    dateAt = (i) => DateTime(targetMonth.year, targetMonth.month, i + 1);
  }

  // Membuat daftar DateTime kosong untuk setiap jam/hari
  final days = List.generate(totalDays, dateAt);

  // Menyusun lookup map agar pencarian data agregasi dari API lebih cepat (O(1))
  final lookup = <DateTime, DailyAverage>{};
  if (range == ChartRange.daily) {
    for (final item in monitoring.hourlyHistory) {
      final key = DateTime(item.date.year, item.date.month, item.date.day, item.date.hour);
      lookup[key] = item;
    }
  } else {
    for (final item in monitoring.dailyHistory) {
      final key = DateTime(item.date.year, item.date.month, item.date.day);
      lookup[key] = item;
    }
  }

  // Menggabungkan kalender kosong dengan data riwayat dari API (jika ada data di jam/hari tersebut)
  return days.map((date) {
    final apiItem = lookup[date];
    if (apiItem != null) {
      return DailyAverage(date: date, suhu: apiItem.suhu, kelembapan: apiItem.kelembapan);
    }
    return DailyAverage(date: date);
  }).toList();
});


