import 'dart:async';
import 'dart:io';

import 'package:finalproject/services/ml_service.dart';
import 'package:finalproject/utils/stock_status.dart';
import 'package:flutter/material.dart';

class ReportController extends ChangeNotifier {
  bool isLoading = true;
  String? errorMessage;

  final List<Map<String, dynamic>> stockItems = [];
  final List<Map<String, dynamic>> stockHistory = [];
  final List<Map<String, dynamic>> predictionItems = [];
  final List<Map<String, dynamic>> criticalItems = [];
  final List<Map<String, dynamic>> usageSummary = [];
  final List<double> demandTrend = [];
  final Map<String, List<Map<String, dynamic>>> _recipeIngredients = {};

  int totalProduk = 0;
  int totalBahan = 0;
  int totalPrediksi = 0;
  int totalKritis = 0;

  int _productCount = 0;

  Timer? _refreshTimer;

  /// Load semua data laporan dari API.
  Future<void> loadReports({bool showLoading = true}) async {
    if (showLoading) {
      isLoading = true;
      notifyListeners();
    }

    try {
      errorMessage = null;

      final results = await Future.wait([
        MLService.getReportStock(),
        MLService.getReportStockIn(),
        MLService.getReportPredictions(),
        MLService.getProducts(),
        MLService.getDashboardSummary(),
        MLService.getRecipes(),
      ]);

      final stockResponse = results[0] as Map<String, dynamic>;
      final stockInResponse = results[1] as Map<String, dynamic>;
      final predictionResponse = results[2] as Map<String, dynamic>;
      final productsResponse = results[3];
      final dashboardSummary = results[4] as Map<String, dynamic>;
      final recipesResponse = results[5];

      if (stockResponse['status'] != true) {
        errorMessage = stockResponse['message']?.toString();
      } else {
        _applyStockItems(stockResponse['data']);
      }

      if (stockInResponse['status'] != true) {
        errorMessage ??= stockInResponse['message']?.toString();
      } else {
        _applyStockHistory(stockInResponse['data']);
      }

      if (predictionResponse['status'] != true) {
        errorMessage ??= predictionResponse['message']?.toString();
      } else {
        _applyPredictions(predictionResponse['data']);
      }

      if (productsResponse is List) {
        _productCount = productsResponse.length;
        _applyCriticalProducts(productsResponse);
      } else {
        _productCount = 0;
        criticalItems.clear();
      }

      if (recipesResponse is List) {
        _applyRecipes(recipesResponse);
      } else {
        _recipeIngredients.clear();
      }

      if (dashboardSummary['status'] != true) {
        errorMessage ??= dashboardSummary['message']?.toString();
        usageSummary.clear();
      } else {
        _applyUsageSummary(dashboardSummary['penggunaan_bahan']);
      }

      if (usageSummary.isEmpty) {
        _rebuildUsageSummaryFromPredictions();
      }

      _rebuildSummary();
      _rebuildDemandTrend();
    } catch (e) {
      errorMessage = 'Gagal memuat laporan: $e';
    } finally {
      isLoading = false;
      notifyListeners();
    }
  }

  /// Refresh otomatis agar laporan selalu realtime.
  void startAutoRefresh() {
    _refreshTimer?.cancel();
    _refreshTimer = Timer.periodic(
      const Duration(minutes: 1),
      (_) => loadReports(showLoading: false),
    );
  }

  void stopAutoRefresh() {
    _refreshTimer?.cancel();
    _refreshTimer = null;
  }

  void _applyStockItems(dynamic data) {
    stockItems.clear();
    if (data is! List) return;
    for (final item in data) {
      stockItems.add({
        'name': item['nama_bahan']?.toString() ?? '-',
        'stock': _toDouble(item['stok']),
        'min_stock': _toDouble(item['stok_minimum']),
        'status': item['status']?.toString() ?? 'Aman',
        'unit': item['unit']?.toString() ?? 'kg',
      });
    }
  }

  void _applyStockHistory(dynamic data) {
    stockHistory.clear();
    if (data is! List) return;
    for (final item in data) {
      stockHistory.add({
        'date': _parseDate(item['tanggal']),
        'name': item['nama_bahan']?.toString() ?? '-',
        'amount': _toDouble(item['jumlah']),
        'unit': item['unit']?.toString() ?? 'kg',
      });
    }
    stockHistory.sort((a, b) {
      final aDate = a['date'] as DateTime;
      final bDate = b['date'] as DateTime;
      return bDate.compareTo(aDate);
    });
  }

  void _applyPredictions(dynamic data) {
    predictionItems.clear();
    if (data is! List) return;
    for (final item in data) {
      final needsValue = item['estimasi_kebutuhan_bahan'];
      final needsText =
          needsValue == null || needsValue.toString().isEmpty
              ? 'Belum tersedia'
              : needsValue.toString();
      predictionItems.add({
        'product': item['nama_produk']?.toString() ?? '-',
        'prediction': _toDouble(item['hasil_prediksi']),
        'needs': needsText,
        'date': _parseDate(item['tanggal_prediksi']),
      });
    }
  }

  void _applyCriticalProducts(List<dynamic> products) {
    criticalItems.clear();

    for (final item in products) {
      if (item is! Map) continue;

      final stock = StockStatusUtils.parseStock(item['current_stock']);
      final minStock = StockStatusUtils.parseStock(item['min_stock']);
      final status = StockStatusUtils.statusFromStock(
        stock,
        minStock: minStock,
      );
      if (status != StockStatusUtils.statusKritis) continue;

      final category = item['category']?.toString().toLowerCase() ?? '';
      final unit =
          item['unit']?.toString() ?? (category == 'barang' ? 'pcs' : 'kg');
      criticalItems.add({
        'name': item['name']?.toString() ?? '-',
        'stock': stock,
        'status': StockStatusUtils.label(status),
        'unit': unit,
      });
    }

    criticalItems.sort(
      (a, b) => a['name'].toString().compareTo(b['name'].toString()),
    );
  }

  void _applyRecipes(List<dynamic> recipes) {
    _recipeIngredients.clear();

    for (final recipe in recipes) {
      if (recipe is! Map) continue;

      final recipeName = recipe['recipe_name']?.toString() ?? '';
      if (recipeName.isEmpty) continue;

      final ingredients = recipe['ingredients'];
      if (ingredients is! List) continue;

      _recipeIngredients[_nameKey(recipeName)] =
          ingredients
              .whereType<Map>()
              .map(
                (ingredient) => {
                  'label': ingredient['product_name']?.toString() ?? '-',
                  'value': _toDouble(ingredient['quantity_needed']),
                  'unit': _normalizeUnit(ingredient['unit']?.toString() ?? ''),
                },
              )
              .where(
                (ingredient) =>
                    (ingredient['label'] as String).isNotEmpty &&
                    (ingredient['value'] as double) > 0,
              )
              .toList();
    }
  }

  void _applyUsageSummary(dynamic data) {
    usageSummary.clear();
    if (data is! List) return;

    for (final item in data) {
      if (item is! Map) continue;

      final label = item['nama_bahan']?.toString() ?? '-';
      final total = _toDouble(item['total_digunakan']);
      final unit = item['satuan']?.toString() ?? 'kg';
      if (total <= 0) continue;

      usageSummary.add({'label': label, 'value': total, 'unit': unit});
    }

    usageSummary.sort(
      (a, b) => (b['value'] as double).compareTo(a['value'] as double),
    );
  }

  void _rebuildUsageSummaryFromPredictions() {
    final Map<String, Map<String, dynamic>> totals = {};

    for (final item in predictionItems) {
      final needs = item['needs']?.toString() ?? '';
      final ingredients =
          needs.isEmpty || needs == 'Belum tersedia'
              ? _estimateUsageFromRecipe(item)
              : _parseEstimatedNeeds(needs);

      for (final ingredient in ingredients) {
        final label = ingredient['label'] as String;
        final normalizedQuantity = _normalizeUsageQuantity(
          value: ingredient['value'] as double,
          unit: ingredient['unit'] as String,
        );
        final unit = normalizedQuantity['unit'] as String;
        final value = normalizedQuantity['value'] as double;
        if (label.isEmpty || value <= 0) continue;

        final key = '${label.toLowerCase()}|$unit';
        final current = totals[key];
        if (current == null) {
          totals[key] = {'label': label, 'value': value, 'unit': unit};
        } else {
          current['value'] = (current['value'] as double) + value;
        }
      }
    }

    usageSummary
      ..clear()
      ..addAll(totals.values);

    usageSummary.sort(
      (a, b) => (b['value'] as double).compareTo(a['value'] as double),
    );

    if (usageSummary.length > 5) {
      usageSummary.removeRange(5, usageSummary.length);
    }
  }

  List<Map<String, dynamic>> _parseEstimatedNeeds(String text) {
    final entries = <Map<String, dynamic>>[];
    final normalized = text.trim();
    if (normalized.isEmpty || normalized == 'Belum tersedia') return entries;

    for (final part in normalized.split(',')) {
      final separatorIndex = part.indexOf(':');
      if (separatorIndex <= 0) continue;

      final label = part.substring(0, separatorIndex).trim();
      final quantityText = part.substring(separatorIndex + 1).trim();
      final match = RegExp(
        r'^([0-9]+(?:[.,][0-9]+)?)\s*([a-zA-Z]+)?',
      ).firstMatch(quantityText);
      if (match == null) continue;

      final value = _toDouble(match.group(1)?.replaceAll(',', '.'));
      final unit = _normalizeUnit(match.group(2) ?? 'unit');
      entries.add({'label': label, 'value': value, 'unit': unit});
    }

    return entries;
  }

  List<Map<String, dynamic>> _estimateUsageFromRecipe(
    Map<String, dynamic> prediction,
  ) {
    final productName = prediction['product']?.toString() ?? '';
    final ingredients = _recipeIngredients[_nameKey(productName)];
    if (ingredients == null || ingredients.isEmpty) return [];

    final predictedQuantity = _toDouble(prediction['prediction']);
    if (predictedQuantity <= 0) return [];

    return ingredients.map((ingredient) {
      return {
        'label': ingredient['label'],
        'value': (ingredient['value'] as double) * predictedQuantity,
        'unit': ingredient['unit'],
      };
    }).toList();
  }

  String _normalizeUnit(String unit) {
    final normalized = unit.trim().toLowerCase();
    if (['g', 'gr', 'gram', 'grams'].contains(normalized)) return 'gr';
    if (['kg', 'kilogram', 'kilograms'].contains(normalized)) return 'kg';
    if (['ml', 'mili', 'mililiter', 'milliliter'].contains(normalized)) {
      return 'ml';
    }
    if (['l', 'lt', 'ltr', 'liter', 'litre'].contains(normalized)) return 'l';
    if (['pcs', 'piece', 'pieces'].contains(normalized)) return 'pcs';
    if (['butir'].contains(normalized)) return 'butir';
    return normalized.isEmpty ? 'unit' : normalized;
  }

  Map<String, dynamic> _normalizeUsageQuantity({
    required double value,
    required String unit,
  }) {
    switch (_normalizeUnit(unit)) {
      case 'gr':
        return {'value': value / 1000, 'unit': 'kg'};
      case 'ml':
        return {'value': value / 1000, 'unit': 'l'};
      default:
        return {'value': value, 'unit': _normalizeUnit(unit)};
    }
  }

  String _nameKey(String value) {
    return value
        .trim()
        .toLowerCase()
        .replaceAll(RegExp(r'\s+'), ' ')
        .replaceAll(
          RegExp(
            r'\s+\d+([,.]\d+)?\s*(kg|kilogram|g|gr|gram|ml|l|liter|ltr|butir|pcs)\b',
            caseSensitive: false,
          ),
          '',
        )
        .trim();
  }

  void _rebuildSummary() {
    totalBahan = stockItems.length;
    totalProduk = _productCount;
    totalPrediksi = predictionItems.length;
    totalKritis = criticalItems.length;
  }

  void _rebuildDemandTrend() {
    final sorted =
        predictionItems.toList()..sort(
          (a, b) => (a['date'] as DateTime).compareTo(b['date'] as DateTime),
        );
    final latestItems =
        sorted.length > 7 ? sorted.skip(sorted.length - 7) : sorted;

    demandTrend
      ..clear()
      ..addAll(latestItems.map((entry) => (entry['prediction'] as double)));
  }

  double _toDouble(dynamic value) {
    if (value is num) return value.toDouble();
    return double.tryParse(value?.toString() ?? '') ?? 0.0;
  }

  DateTime _parseDate(dynamic value) {
    if (value is DateTime) return value;
    if (value is String) {
      final text = value.trim();
      if (text.isEmpty) return DateTime.fromMillisecondsSinceEpoch(0);

      final parsed = DateTime.tryParse(text);
      if (parsed != null) return parsed;

      try {
        return HttpDate.parse(text).toLocal();
      } catch (_) {
        return DateTime.fromMillisecondsSinceEpoch(0);
      }
    }
    return DateTime.fromMillisecondsSinceEpoch(0);
  }

  @override
  void dispose() {
    stopAutoRefresh();
    super.dispose();
  }
}
