import '../data/disease_data.dart';
import '../models/symptom.dart';
import '../models/disease.dart';

class FuzzyMamdani {
  Map<String, double> calculateMembership(double value) {
    if (value < 0 || value > 1) {
      throw ArgumentError(
          'Nilai bobot gejala harus berada dalam rentang 0 – 1');
    }

    Map<String, double> memberships = {
      'rendah': 0.0,
      'sedang': 0.0,
      'tinggi': 0.0,
    };

    if (value <= 0.4) {
      memberships['rendah'] = 1.0;
    } else if (value <= 0.5) {
      memberships['rendah'] = (0.5 - value) / (0.5 - 0.4);
    }

    if (value >= 0.4 && value <= 0.5) {
      memberships['sedang'] = (value - 0.4) / (0.5 - 0.4);
    } else if (value > 0.5 && value <= 0.7) {
      memberships['sedang'] = 1.0;
    } else if (value > 0.7 && value <= 0.8) {
      memberships['sedang'] = (0.8 - value) / (0.8 - 0.7);
    }

    if (value >= 0.7 && value <= 0.8) {
      memberships['tinggi'] = (value - 0.7) / (0.8 - 0.7);
    } else if (value > 0.8) {
      memberships['tinggi'] = 1.0;
    }

    return memberships;
  }

  Map<String, Map<String, double>> fuzzify(Map<String, double> userInputs) {
    if (userInputs.isEmpty) {
      throw ArgumentError('Input gejala tidak boleh kosong');
    }
    for (var symptomId in userInputs.keys) {
      if (!symptoms.any((s) => s.id == symptomId)) {
        throw ArgumentError('ID gejala tidak valid: $symptomId');
      }
    }

    Map<String, Map<String, double>> memberships = {};
    userInputs.forEach((symptomId, value) {
      memberships[symptomId] = calculateMembership(value);
    });
    return memberships;
  }

  Map<String, Map<String, dynamic>> forwardChaining(
      Map<String, Map<String, double>> memberships) {
    Map<String, Map<String, dynamic>> diseaseResults = {};
    Map<String, Symptom> symptomMap = {
      for (var symptom in symptoms) symptom.id: symptom
    };

    for (var rule in rules) {
      int totalSymptoms = rule.symptomIds.length;
      int matchedSymptoms = 0;
      List<String> matchedSymptomIds = [];
      Map<String, double> totalMembership = {
        'rendah': 0.0,
        'sedang': 0.0,
        'tinggi': 0.0,
      };
      double totalWeight = 0.0;

      for (var symptomId in rule.symptomIds) {
        if (memberships.containsKey(symptomId)) {
          matchedSymptoms++;
          matchedSymptomIds.add(symptomId);
          totalMembership['rendah'] =
              totalMembership['rendah']! + memberships[symptomId]!['rendah']!;
          totalMembership['sedang'] =
              totalMembership['sedang']! + memberships[symptomId]!['sedang']!;
          totalMembership['tinggi'] =
              totalMembership['tinggi']! + memberships[symptomId]!['tinggi']!;
          totalWeight += symptomMap[symptomId]!.weight;
        }
      }

      if (matchedSymptoms > 0) {
        double matchPercentage = matchedSymptoms / totalSymptoms;
        double weightFactor = totalWeight / matchedSymptoms;
        Map<String, double> ruleScores = {
          'rendah': (totalMembership['rendah']! / matchedSymptoms) *
              matchPercentage *
              weightFactor,
          'sedang': (totalMembership['sedang']! / matchedSymptoms) *
              matchPercentage *
              weightFactor,
          'tinggi': (totalMembership['tinggi']! / matchedSymptoms) *
              matchPercentage *
              weightFactor,
        };
        diseaseResults[rule.diseaseId] = {
          'scores': ruleScores,
          'matchPercentage': matchPercentage,
          'matchedSymptoms': matchedSymptomIds,
          'totalWeight': totalWeight,
        };
      }
    }

    Map<String, String> recommendations = {};
    Map<String, double> tempConfidenceScores = defuzzify(diseaseResults);

    diseaseResults.forEach((diseaseId, data) {
      double confidence = tempConfidenceScores[diseaseId] ?? 0.0;
      double totalWeight = data['totalWeight'] as double;
      List<String> matchedSymptoms = data['matchedSymptoms'] as List<String>;

      if (diseaseId == 'P1') {
        if (confidence > 40 && totalWeight > 1.5) {
          recommendations[diseaseId] =
              'Segera lakukan penyemprotan intensif dengan fungisida difenokonazol setiap 5 hari.';
        } else {
          recommendations[diseaseId] =
              'Gunakan fungisida difenokonazol dengan interval penyemprotan 7 hari.';
        }
      } else if (diseaseId == 'P2') {
        if (confidence > 30 && matchedSymptoms.contains('GP15')) {
          recommendations[diseaseId] =
              'Lakukan pengendalian dengan insektisida tambahan karena ada indikasi serangan hama.';
        } else {
          recommendations[diseaseId] =
              'Gunakan mulsa plastik untuk mencegah penyebaran.';
        }
      } else if (diseaseId == 'P3') {
        if (confidence > 50 && totalWeight > 2.0) {
          recommendations[diseaseId] =
              'Segera lakukan drainase lahan dan gunakan fungisida berbahan aktif metalaksil.';
        } else {
          recommendations[diseaseId] =
              'Gunakan fungisida berbahan aktif metalaksil dengan interval 7 hari.';
        }
      } else if (diseaseId == 'P4') {
        if (confidence > 40 && matchedSymptoms.contains('GP9')) {
          recommendations[diseaseId] =
              'Gunakan fungisida berbahan aktif azoksistrobin dan lakukan sanitasi lahan.';
        } else {
          recommendations[diseaseId] =
              'Gunakan fungisida berbahan aktif azoksistrobin dengan interval 7 hari.';
        }
      } else if (diseaseId == 'P5') {
        if (confidence > 30 && matchedSymptoms.contains('GP16')) {
          recommendations[diseaseId] =
              'Lakukan pemangkasan daun yang terinfeksi dan gunakan fungisida berbahan aktif mankozeb.';
        } else {
          recommendations[diseaseId] =
              'Gunakan fungisida berbahan aktif mankozeb dengan interval 7 hari.';
        }
      } else if (diseaseId == 'P6') {
        if (confidence > 50 && matchedSymptoms.contains('GP18')) {
          recommendations[diseaseId] =
              'Lakukan penggantian media tanam dan gunakan fungisida berbahan aktif karbendazim.';
        } else {
          recommendations[diseaseId] =
              'Gunakan fungisida berbahan aktif karbendazim dengan interval 7 hari.';
        }
      } else if (diseaseId == 'P7') {
        if (confidence > 50 && matchedSymptoms.contains('GP20')) {
          recommendations[diseaseId] =
              'Lakukan pembakaran tanaman yang terinfeksi dan gunakan bakterisida berbahan aktif streptomisin.';
        } else {
          recommendations[diseaseId] =
              'Gunakan bakterisida berbahan aktif streptomisin dengan interval 7 hari.';
        }
      }
    });

    List<String> highConfidenceDiseases = tempConfidenceScores.entries
        .where((entry) => entry.value > 30)
        .map((entry) => entry.key)
        .toList();

    if (highConfidenceDiseases.length > 1) {
      List<String> diseaseNames = highConfidenceDiseases.map((diseaseId) {
        final disease = diseases.firstWhere(
          (d) => d.id == diseaseId,
          orElse: () => Disease(
              id: diseaseId,
              name: 'Penyakit Tidak Diketahui',
              symptomIds: [],
              solutions: []),
        );
        return disease.name;
      }).toList();

      String generalRecommendation =
          'Konsultasikan dengan ahli pertanian karena ada indikasi infeksi ganda (${diseaseNames.join(", ")}).';
      highConfidenceDiseases.forEach((diseaseId) {
        recommendations[diseaseId] =
            (recommendations[diseaseId] ?? '') + '\n$generalRecommendation';
      });
    }

    diseaseResults.forEach((diseaseId, data) {
      data['recommendation'] = recommendations[diseaseId] ?? '';
    });

    return diseaseResults;
  }

  Map<String, double> defuzzify(
      Map<String, Map<String, dynamic>> diseaseResults) {
    Map<String, double> results = {};

    const double lowValue = 20;
    const double mediumValue = 60;
    const double highValue = 90;

    diseaseResults.forEach((diseaseId, data) {
      Map<String, double> scores = data['scores'] as Map<String, double>;
      double matchPercentage = data['matchPercentage'] as double;

      double numerator = 0.0;
      double denominator = 0.0;

      numerator += lowValue * scores['rendah']!;
      numerator += mediumValue * scores['sedang']!;
      numerator += highValue * scores['tinggi']!;

      denominator += scores['rendah']! + scores['sedang']! + scores['tinggi']!;

      if (denominator == 0) {
        results[diseaseId] = 0.0;
      } else {
        double centroid = numerator / denominator;
        double adjustmentFactor = 2.4 + (1.05 * (1 - matchPercentage));
        double confidence = (centroid * matchPercentage) * adjustmentFactor;
        results[diseaseId] = confidence.clamp(0, 100);
        results[diseaseId] =
            double.parse(results[diseaseId]!.toStringAsFixed(1));
      }
    });

    return results;
  }

  Map<String, Map<String, dynamic>> diagnose(Map<String, double> userInputs) {
    var memberships = fuzzify(userInputs);
    var diseaseResults = forwardChaining(memberships);
    var confidenceScores = defuzzify(diseaseResults);

    Map<String, Map<String, dynamic>> finalResults = {};
    diseaseResults.forEach((diseaseId, data) {
      finalResults[diseaseId] = {
        'confidence': confidenceScores[diseaseId] ?? 0.0,
        'matchPercentage': data['matchPercentage'],
        'matchedSymptoms': data['matchedSymptoms'],
        'recommendation': data['recommendation'],
        'totalWeight': data['totalWeight'],
      };
    });

    return finalResults;
  }
}
