@Override
  public String enroll(
      String externalId,
      String scheduleName,
      String startingMilestoneName,
      DateTime referenceDateTime,
      DateTime enrollmentDateTime,
      Time preferredAlertTime,
      Map<String, String> metadata) {
    Schedule schedule = allSchedules.getByName(scheduleName);
    Enrollment enrollment =
        new Enrollment()
            .setExternalId(externalId)
            .setSchedule(schedule)
            .setCurrentMilestoneName(startingMilestoneName)
            .setStartOfSchedule(referenceDateTime)
            .setEnrolledOn(enrollmentDateTime)
            .setPreferredAlertTime(preferredAlertTime)
            .setStatus(EnrollmentStatus.ACTIVE)
            .setMetadata(metadata);

    if (schedule.hasExpiredSince(
        enrollment.getCurrentMilestoneStartDate(), startingMilestoneName)) {
      enrollment.setStatus(EnrollmentStatus.DEFAULTED);
    }

    Enrollment activeEnrollment = allEnrollments.getActiveEnrollment(externalId, scheduleName);
    if (activeEnrollment == null) {
      allEnrollments.add(enrollment);
      eventRelay.sendEventMessage(
          new EnrolledUserEvent(
                  enrollment.getExternalId(),
                  enrollment.getScheduleName(),
                  enrollment.getPreferredAlertTime(),
                  referenceDateTime,
                  enrollmentDateTime,
                  enrollment.getCurrentMilestoneName())
              .toMotechEvent());
    } else {
      unscheduleJobs(activeEnrollment);
      enrollment = activeEnrollment.copyFrom(enrollment);
      allEnrollments.update(enrollment);
    }

    scheduleJobs(enrollment);
    return enrollment.getId();
  }
  @Override
  public void fulfillCurrentMilestone(Enrollment enrollment, DateTime fulfillmentDateTime) {
    Schedule schedule = allSchedules.getByName(enrollment.getScheduleName());
    if (isNullOrEmpty(enrollment.getCurrentMilestoneName())) {
      throw new NoMoreMilestonesToFulfillException();
    }

    unscheduleJobs(enrollment);

    enrollment.fulfillCurrentMilestone(fulfillmentDateTime);
    String nextMilestoneName = schedule.getNextMilestoneName(enrollment.getCurrentMilestoneName());
    enrollment.setCurrentMilestoneName(nextMilestoneName);
    if (nextMilestoneName == null) {
      enrollment.setStatus(COMPLETED);
    } else {
      scheduleJobs(enrollment);
    }

    allEnrollments.update(enrollment);
  }
 @Override
 public MilestoneAlerts getAlertTimings(
     String externalId,
     String scheduleName,
     String milestoneName,
     DateTime referenceDateTime,
     DateTime enrollmentDateTime,
     Time preferredAlertTime) {
   Schedule schedule = allSchedules.getByName(scheduleName);
   return enrollmentAlertService.getAlertTimings(
       new Enrollment()
           .setExternalId(externalId)
           .setSchedule(schedule)
           .setCurrentMilestoneName(milestoneName)
           .setStartOfSchedule(referenceDateTime)
           .setEnrolledOn(enrollmentDateTime)
           .setPreferredAlertTime(preferredAlertTime)
           .setStatus(EnrollmentStatus.ACTIVE)
           .setMetadata(null));
 }
 @After
 public void teardown() {
   schedulerService.unscheduleAllJobs("org.motechproject.scheduletracking");
   allEnrollments.removeAll();
   allSchedules.removeAll();
 }