import 'package:em2rp/models/event_model.dart';
import 'package:em2rp/models/equipment_model.dart';
import 'package:em2rp/models/container_model.dart';
import 'package:em2rp/services/api_service.dart';
import 'package:em2rp/services/data_service.dart';

/// Type de conflit
enum ConflictType {
  equipmentUnavailable,   // Équipement non quantifiable utilisé
  insufficientQuantity,   // Quantité insuffisante pour consommable/câble
  containerFullyUsed,     // Boîte complète utilisée
  containerPartiallyUsed, // Certains équipements de la boîte utilisés
}

/// Informations sur un conflit de disponibilité
class AvailabilityConflict {
  final String equipmentId;
  final String equipmentName;
  final EventModel conflictingEvent;
  final int overlapDays;
  final ConflictType type;

  // Pour les quantités (consommables/câbles)
  final int? totalQuantity;
  final int? availableQuantity;
  final int? requestedQuantity;
  final int? reservedQuantity;

  // Pour les boîtes
  final String? containerId;
  final String? containerName;
  final List<String>? conflictingChildrenIds;

  AvailabilityConflict({
    required this.equipmentId,
    required this.equipmentName,
    required this.conflictingEvent,
    required this.overlapDays,
    this.type = ConflictType.equipmentUnavailable,
    this.totalQuantity,
    this.availableQuantity,
    this.requestedQuantity,
    this.reservedQuantity,
    this.containerId,
    this.containerName,
    this.conflictingChildrenIds,
  });

  /// Message descriptif du conflit
  String get conflictMessage {
    switch (type) {
      case ConflictType.equipmentUnavailable:
        return 'Équipement déjà utilisé';
      case ConflictType.insufficientQuantity:
        return 'Stock insuffisant : $availableQuantity/$totalQuantity disponible (demandé: $requestedQuantity)';
      case ConflictType.containerFullyUsed:
        return 'Boîte complète déjà utilisée';
      case ConflictType.containerPartiallyUsed:
        final count = conflictingChildrenIds?.length ?? 0;
        return '$count équipement(s) de la boîte déjà utilisé(s)';
    }
  }
}

/// Service pour vérifier la disponibilité du matériel
class EventAvailabilityService {
  final DataService _dataService = DataService(apiService);

  /// Helper pour récupérer uniquement la liste d'événements
  Future<List<Map<String, dynamic>>> _getEventsList() async {
    final result = await _dataService.getEvents();
    final events = result['events'] as List<dynamic>? ?? [];
    return events.map((e) => e as Map<String, dynamic>).toList();
  }

  /// Vérifie si un équipement est disponible pour une plage de dates via Cloud Function
  Future<List<AvailabilityConflict>> checkEquipmentAvailability({
    required String equipmentId,
    required String equipmentName,
    required DateTime startDate,
    required DateTime endDate,
    String? excludeEventId, // Pour exclure l'événement en cours d'édition
  }) async {
    final conflicts = <AvailabilityConflict>[];

    try {
      print('[EventAvailabilityService] Checking availability for equipment $equipmentId ($equipmentName)');

      // Utiliser la Cloud Function pour vérifier la disponibilité
      final result = await _dataService.checkEquipmentAvailability(
        equipmentId: equipmentId,
        startDate: startDate,
        endDate: endDate,
        excludeEventId: excludeEventId,
      );

      print('[EventAvailabilityService] Result for $equipmentId: $result');

      final available = result['available'] as bool? ?? true;
      print('[EventAvailabilityService] Equipment $equipmentId available: $available');

      if (!available) {
        final conflictsData = result['conflicts'] as List<dynamic>? ?? [];
        print('[EventAvailabilityService] Found ${conflictsData.length} conflicts for equipment $equipmentId');

        for (final conflictData in conflictsData) {
          final conflict = conflictData as Map<String, dynamic>;
          final eventId = conflict['eventId'] as String;

          // Le backend retourne déjà eventData
          final eventData = conflict['eventData'] as Map<String, dynamic>?;

          if (eventData != null && eventData.isNotEmpty) {
            try {
              final event = EventModel.fromMap(eventData, eventId);
              conflicts.add(AvailabilityConflict(
                equipmentId: equipmentId,
                equipmentName: equipmentName,
                conflictingEvent: event,
                overlapDays: conflict['overlapDays'] as int? ?? 0,
              ));
              print('[EventAvailabilityService] Added conflict with event ${event.name}');
            } catch (e) {
              print('[EventAvailabilityService] Error creating EventModel: $e');
              print('[EventAvailabilityService] EventData: $eventData');
            }
          }
        }
      }
    } catch (e) {
      print('[EventAvailabilityService] Error checking availability: $e');
    }

    print('[EventAvailabilityService] Returning ${conflicts.length} conflicts for equipment $equipmentId');
    return conflicts;
  }

  /// Vérifie la disponibilité pour une liste d'équipements
  Future<Map<String, List<AvailabilityConflict>>> checkMultipleEquipmentAvailability({
    required List<String> equipmentIds,
    required Map<String, String> equipmentNames,
    required DateTime startDate,
    required DateTime endDate,
    String? excludeEventId,
  }) async {
    final allConflicts = <String, List<AvailabilityConflict>>{};

    for (var equipmentId in equipmentIds) {
      final conflicts = await checkEquipmentAvailability(
        equipmentId: equipmentId,
        equipmentName: equipmentNames[equipmentId] ?? equipmentId,
        startDate: startDate,
        endDate: endDate,
        excludeEventId: excludeEventId,
      );

      if (conflicts.isNotEmpty) {
        allConflicts[equipmentId] = conflicts;
      }
    }


    return allConflicts;
  }

  /// Vérifie si deux plages de dates se chevauchent
  bool _datesOverlap(DateTime start1, DateTime end1, DateTime start2, DateTime end2) {
    // Deux plages se chevauchent si elles ne sont PAS complètement séparées
    // Elles sont séparées si : end1 < start2 OU end2 < start1
    // Donc elles se chevauchent si : NOT (end1 < start2 OU end2 < start1)
    // Équivalent à : end1 >= start2 ET end2 >= start1
    return !end1.isBefore(start2) && !end2.isBefore(start1);
  }

  /// Calcule le nombre de jours de chevauchement
  int _calculateOverlapDays(DateTime start1, DateTime end1, DateTime start2, DateTime end2) {
    final overlapStart = start1.isAfter(start2) ? start1 : start2;
    final overlapEnd = end1.isBefore(end2) ? end1 : end2;

    return overlapEnd.difference(overlapStart).inDays + 1;
  }

  /// Récupère la quantité disponible pour un consommable/câble
  Future<int> getAvailableQuantity({
    required EquipmentModel equipment,
    required DateTime startDate,
    required DateTime endDate,
    String? excludeEventId,
  }) async {
    if (!equipment.hasQuantity) {
      return 1; // Équipement non consommable
    }

    final totalQuantity = equipment.totalQuantity ?? 0;
    int reservedQuantity = 0;

    try {
      // Récupérer tous les événements via Cloud Function
      final eventsData = await _getEventsList();

      for (var eventData in eventsData) {
        final eventId = eventData['id'] as String;
        if (excludeEventId != null && eventId == excludeEventId) {
          continue;
        }

        try {
          final event = EventModel.fromMap(eventData, eventId);

          // Ignorer les événements annulés
          if (event.status == EventStatus.canceled) {
            continue;
          }

          // Calculer les dates réelles avec temps d'installation et démontage
          final eventRealStartDate = event.startDateTime.subtract(
            Duration(hours: event.installationTime),
          );
          final eventRealEndDate = event.endDateTime.add(
            Duration(hours: event.disassemblyTime),
          );

          // Vérifier le chevauchement des dates
          if (_datesOverlap(startDate, endDate, eventRealStartDate, eventRealEndDate)) {
            final assignedEquipment = event.assignedEquipment.firstWhere(
              (eq) => eq.equipmentId == equipment.id,
              orElse: () => EventEquipment(equipmentId: ''),
            );

            // Si l'équipement est assigné, réserver la quantité
            // (peu importe le statut de préparation/retour)
            if (assignedEquipment.equipmentId.isNotEmpty) {
              reservedQuantity += assignedEquipment.quantity;
            }
          }
        } catch (e) {
          print('[EventAvailabilityService] Error processing event $eventId for quantity: $e');
        }
      }
    } catch (e) {
      print('[EventAvailabilityService] Error getting available quantity: $e');
    }

    return totalQuantity - reservedQuantity;
  }

  /// Vérifie la disponibilité d'un équipement avec gestion des quantités
  Future<List<AvailabilityConflict>> checkEquipmentAvailabilityWithQuantity({
    required EquipmentModel equipment,
    required int requestedQuantity,
    required DateTime startDate,
    required DateTime endDate,
    String? excludeEventId,
  }) async {
    final conflicts = <AvailabilityConflict>[];

    // Si équipement quantifiable (consommable/câble)
    if (equipment.hasQuantity) {
      final totalQuantity = equipment.totalQuantity ?? 0;
      final availableQty = await getAvailableQuantity(
        equipment: equipment,
        startDate: startDate,
        endDate: endDate,
        excludeEventId: excludeEventId,
      );
      final reservedQty = totalQuantity - availableQty;

      // ✅ Ne créer un conflit que si la quantité est VRAIMENT insuffisante
      if (availableQty < requestedQuantity) {
        // Trouver les événements qui réservent cette quantité
        final eventsData = await _getEventsList();

        for (var eventData in eventsData) {
          final eventId = eventData['id'] as String;
          if (excludeEventId != null && eventId == excludeEventId) continue;

          try {
            final event = EventModel.fromMap(eventData, eventId);

            if (_datesOverlap(startDate, endDate, event.startDateTime, event.endDateTime)) {
              final assignedEquipment = event.assignedEquipment.firstWhere(
                (eq) => eq.equipmentId == equipment.id,
                orElse: () => EventEquipment(equipmentId: ''),
              );

              if (assignedEquipment.equipmentId.isNotEmpty && !assignedEquipment.isReturned) {
                conflicts.add(AvailabilityConflict(
                  equipmentId: equipment.id,
                  equipmentName: equipment.name,
                  conflictingEvent: event,
                  overlapDays: _calculateOverlapDays(startDate, endDate, event.startDateTime, event.endDateTime),
                  type: ConflictType.insufficientQuantity,
                  totalQuantity: totalQuantity,
                  availableQuantity: availableQty,
                  requestedQuantity: requestedQuantity,
                  reservedQuantity: reservedQty,
                ));
              }
            }
          } catch (e) {
            print('[EventAvailabilityService] Error processing event $eventId: $e');
          }
        }
      }
    } else {
      // Équipement non quantifiable : vérification classique
      return await checkEquipmentAvailability(
        equipmentId: equipment.id,
        equipmentName: equipment.name,
        startDate: startDate,
        endDate: endDate,
        excludeEventId: excludeEventId,
      );
    }

    return conflicts;
  }

  /// Vérifie la disponibilité d'une boîte et de son contenu
  Future<List<AvailabilityConflict>> checkContainerAvailability({
    required ContainerModel container,
    required List<EquipmentModel> containerEquipment,
    required DateTime startDate,
    required DateTime endDate,
    String? excludeEventId,
  }) async {
    final conflicts = <AvailabilityConflict>[];
    final conflictingChildrenIds = <String>[];

    // Vérifier d'abord si la boîte complète est utilisée
    final eventsData = await _getEventsList();
    bool isContainerFullyUsed = false;
    EventModel? containerConflictingEvent;

    for (var eventData in eventsData) {
      final eventId = eventData['id'] as String;
      if (excludeEventId != null && eventId == excludeEventId) continue;

      try {
        final event = EventModel.fromMap(eventData, eventId);

        // Ignorer les événements annulés
        if (event.status == EventStatus.canceled) {
          continue;
        }

        // Calculer les dates réelles avec temps d'installation et démontage
        final eventRealStartDate = event.startDateTime.subtract(
          Duration(hours: event.installationTime),
        );
        final eventRealEndDate = event.endDateTime.add(
          Duration(hours: event.disassemblyTime),
        );

        // Vérifier si cette boîte est assignée
        if (event.assignedContainers.contains(container.id)) {
          if (_datesOverlap(startDate, endDate, eventRealStartDate, eventRealEndDate)) {
            isContainerFullyUsed = true;
            containerConflictingEvent = event;
            break;
          }
        }
      } catch (e) {
        print('[EventAvailabilityService] Error processing event $eventId: $e');
      }
    }

    if (isContainerFullyUsed && containerConflictingEvent != null) {
      // Boîte complète utilisée
      conflicts.add(AvailabilityConflict(
        equipmentId: container.id,
        equipmentName: container.name,
        conflictingEvent: containerConflictingEvent,
        overlapDays: _calculateOverlapDays(
          startDate,
          endDate,
          containerConflictingEvent.startDateTime,
          containerConflictingEvent.endDateTime,
        ),
        type: ConflictType.containerFullyUsed,
        containerId: container.id,
        containerName: container.name,
      ));
    } else {
      // Vérifier chaque équipement enfant individuellement
      for (var equipment in containerEquipment) {
        final equipmentConflicts = await checkEquipmentAvailability(
          equipmentId: equipment.id,
          equipmentName: equipment.name,
          startDate: startDate,
          endDate: endDate,
          excludeEventId: excludeEventId,
        );

        if (equipmentConflicts.isNotEmpty) {
          conflictingChildrenIds.add(equipment.id);
          conflicts.addAll(equipmentConflicts);
        }
      }

      // Si au moins un enfant en conflit, ajouter un conflit pour la boîte
      if (conflictingChildrenIds.isNotEmpty && conflicts.isNotEmpty) {
        conflicts.insert(
          0,
          AvailabilityConflict(
            equipmentId: container.id,
            equipmentName: container.name,
            conflictingEvent: conflicts.first.conflictingEvent,
            overlapDays: conflicts.first.overlapDays,
            type: ConflictType.containerPartiallyUsed,
            containerId: container.id,
            containerName: container.name,
            conflictingChildrenIds: conflictingChildrenIds,
          ),
        );
      }
    }

    return conflicts;
  }
}


