import '../modelos/alarma_musical.dart';

class ServicioProgramacionAlarmas {
  DateTime? calcularProxima({
    required AlarmaMusical alarma,
    required DateTime desde,
    List<RangoVacaciones> vacaciones = const [],
    List<ExcepcionAlarma> excepciones = const [],
  }) {
    if (!alarma.activa) return null;

    final diaBase =
        alarma.tipoProgramacion == TipoProgramacionAlarma.unica &&
                alarma.fechaUnica != null
            ? alarma.fechaUnica!
            : desde;
    final inicio = DateTime(
      diaBase.year,
      diaBase.month,
      diaBase.day,
      alarma.hora,
      alarma.minuto,
    );
    final primerCandidato =
        alarma.tipoProgramacion == TipoProgramacionAlarma.unica
            ? inicio
            : inicio.isAfter(desde)
            ? inicio
            : inicio.add(const Duration(days: 1));

    return switch (alarma.tipoProgramacion) {
      TipoProgramacionAlarma.unica =>
        primerCandidato.isAfter(desde) &&
                _esValida(alarma, primerCandidato, vacaciones, excepciones)
            ? primerCandidato
            : null,
      TipoProgramacionAlarma.diaria => _buscarDiaria(
        alarma,
        primerCandidato,
        vacaciones,
        excepciones,
      ),
      TipoProgramacionAlarma.diasSemana => _buscarPorDiasSemana(
        alarma,
        primerCandidato,
        vacaciones,
        excepciones,
      ),
    };
  }

  DateTime calcularSnooze(DateTime desde, int minutos) {
    final seguro = minutos == 3 || minutos == 5 || minutos == 10 ? minutos : 5;
    return desde.add(Duration(minutes: seguro));
  }

  bool estaEnVacaciones(DateTime fecha, List<RangoVacaciones> vacaciones) =>
      vacaciones.any((rango) => rango.contiene(fecha));

  DateTime? _buscarDiaria(
    AlarmaMusical alarma,
    DateTime candidato,
    List<RangoVacaciones> vacaciones,
    List<ExcepcionAlarma> excepciones,
  ) {
    var actual = candidato;
    for (var i = 0; i < 370; i++) {
      if (_esValida(alarma, actual, vacaciones, excepciones)) return actual;
      actual = actual.add(const Duration(days: 1));
    }
    return null;
  }

  DateTime? _buscarPorDiasSemana(
    AlarmaMusical alarma,
    DateTime candidato,
    List<RangoVacaciones> vacaciones,
    List<ExcepcionAlarma> excepciones,
  ) {
    if (alarma.diasSemana.isEmpty) return null;
    var actual = candidato;
    for (var i = 0; i < 370; i++) {
      if (alarma.diasSemana.contains(actual.weekday) &&
          _esValida(alarma, actual, vacaciones, excepciones)) {
        return actual;
      }
      actual = actual.add(const Duration(days: 1));
    }
    return null;
  }

  bool _esValida(
    AlarmaMusical alarma,
    DateTime candidato,
    List<RangoVacaciones> vacaciones,
    List<ExcepcionAlarma> excepciones,
  ) {
    if (!alarma.sonarEnVacaciones && estaEnVacaciones(candidato, vacaciones)) {
      return false;
    }
    return !excepciones.any(
      (excepcion) =>
          excepcion.alarmaId == alarma.id &&
          _mismaEjecucion(excepcion.ejecucion, candidato),
    );
  }

  bool _mismaEjecucion(DateTime a, DateTime b) =>
      a.year == b.year &&
      a.month == b.month &&
      a.day == b.day &&
      a.hour == b.hour &&
      a.minute == b.minute;
}
