@Override
  public void generateIvrAndSmsStatisticReports() {
    Config config = configService.getConfig();

    if (StringUtils.isNotBlank(config.getLastCalculationDateForIvrReports())) {
      LocalDate startDate =
          SIMPLE_DATE_FORMATTER.parseLocalDate(config.getLastCalculationDateForIvrReports());
      generateIvrAndSmsStatisticReportsFromDate(startDate);
    } else {
      generateIvrAndSmsStatisticReportsFromDate(null);
    }

    config = configService.getConfig();
    config.setLastCalculationDateForIvrReports(LocalDate.now().toString(SIMPLE_DATE_FORMATTER));
    configService.updateConfig(config);
  }
  @Override
  public void generateDailyReports() {
    DateTimeFormatter formatter = DateTimeFormat.forPattern(EbodacConstants.REPORT_DATE_FORMAT);

    Config config = configService.getConfig();

    if (config.getGenerateReports() != null && config.getGenerateReports()) {
      String lastCalculationDate = config.getLastCalculationDate();
      String calculationStartDate = config.getFirstCalculationStartDate();

      LocalDate startDate;

      if (StringUtils.isNotBlank(lastCalculationDate)) {
        startDate = LocalDate.parse(lastCalculationDate, formatter).plusDays(1);
      } else if (StringUtils.isNotBlank(calculationStartDate)) {
        startDate = LocalDate.parse(calculationStartDate, formatter);
      } else {
        startDate = subjectService.findOldestPrimerVaccinationDate();
      }

      updateBoosterVaccinationReportsForDates(
          reportUpdateService.getBoosterVaccinationReportsToUpdate());
      updatePrimerVaccinationReportsForDates(
          reportUpdateService.getPrimerVaccinationReportsToUpdate());

      generateDailyReportsFromDate(startDate);

      config = configService.getConfig();
      config.setGenerateReports(false);
      config.setLastCalculationDate(DateUtil.now().minusDays(1).toString(formatter));
      configService.updateConfig(config);
    }
  }
  @Override
  public void generateIvrAndSmsStatisticReportsFromDate(LocalDate startDate) {
    List<CallDetailRecord> callDetailRecords = new ArrayList<>();

    if (startDate == null) {
      callDetailRecords =
          callDetailRecordDataService.findByCallStatus(
              EbodacConstants.IVR_CALL_DETAIL_RECORD_STATUS_INITIATED);
    } else {
      LocalDate now = DateUtil.now().toLocalDate();

      for (LocalDate date = startDate; date.isBefore(now); date = date.plusDays(1)) {
        String dateString = SIMPLE_DATE_FORMATTER.print(date);
        callDetailRecords.addAll(
            callDetailRecordDataService.findByMotechTimestampAndCallStatus(
                dateString, EbodacConstants.IVR_CALL_DETAIL_RECORD_STATUS_INITIATED));
      }
    }

    Config config = configService.getConfig();
    Set<String> reportsToUpdate = config.getIvrAndSmsStatisticReportsToUpdate();
    config.setIvrAndSmsStatisticReportsToUpdate(null);
    configService.updateConfig(config);

    if (startDate != null && !reportsToUpdate.isEmpty()) {
      callDetailRecords.addAll(callDetailRecordDataService.findByMotechCallIds(reportsToUpdate));
    }

    for (CallDetailRecord callDetailRecord : callDetailRecords) {
      try {
        createIvrAndSmsStatisticReport(callDetailRecord);
        reportsToUpdate.remove(callDetailRecord.getMotechCallId());
      } catch (EbodacReportException e) {
        LOGGER.warn(e.getMessage());
      }
    }

    config = configService.getConfig();
    reportsToUpdate.addAll(config.getIvrAndSmsStatisticReportsToUpdate());
    config.setIvrAndSmsStatisticReportsToUpdate(reportsToUpdate);
    configService.updateConfig(config);
  }
  private void createIvrAndSmsStatisticReport(
      CallDetailRecord initialRecord) { // NO CHECKSTYLE CyclomaticComplexity
    DateTimeFormatter motechTimestampFormatter =
        DateTimeFormat.forPattern(EbodacConstants.IVR_CALL_DETAIL_RECORD_TIME_FORMAT);
    DateTimeFormatter votoTimestampFormatter =
        DateTimeFormat.forPattern(EbodacConstants.VOTO_TIMESTAMP_FORMAT);

    String providerCallId = initialRecord.getProviderCallId();
    Map<String, String> providerExtraData = initialRecord.getProviderExtraData();

    if (StringUtils.isBlank(providerCallId)) {
      throw new EbodacReportException(
          "Cannot generate report for Call Detail Record with Motech Call Id: %s, because Provider Call Id is empty",
          "", initialRecord.getMotechCallId());
    }
    if (providerExtraData == null || providerExtraData.isEmpty()) {
      throw new EbodacReportException(
          "Cannot generate report for Call Detail Record with Motech Call Id: %s, because Provider Extra Data Map is empty",
          "", initialRecord.getMotechCallId());
    }

    String subjectIds = providerExtraData.get(EbodacConstants.SUBJECT_IDS);
    List<Subject> subjects = new ArrayList<>();

    if (StringUtils.isBlank(subjectIds)) {
      throw new EbodacReportException(
          "Cannot generate report for Call Detail Record with Motech Call Id: %s, because no ParticipantId found In Provider Extra Data Map",
          "", initialRecord.getMotechCallId());
    }

    for (String subjectId : subjectIds.split(",")) {
      Subject subject = subjectService.findSubjectBySubjectId(subjectId.trim());
      if (subject != null) {
        subjects.add(subject);
      } else {
        LOGGER.warn(
            "Cannot generate report for Provider Call Id: {} and ParticipantId: {}, because no Participant with this Id found",
            providerCallId,
            subjectId);
      }
    }

    if (subjects.isEmpty()) {
      throw new EbodacReportException(
          "Cannot generate report for Call Detail Record with Motech Call Id: %s, because No Participants found with Ids: %s",
          "", initialRecord.getMotechCallId(), subjectIds);
    }

    List<CallDetailRecord> callDetailRecords =
        callDetailRecordDataService.findByExactProviderCallId(
            providerCallId,
            QueryParams.ascOrder(EbodacConstants.IVR_CALL_DETAIL_RECORD_MOTECH_TIMESTAMP_FIELD));

    List<CallDetailRecord> failed = new ArrayList<>();
    List<CallDetailRecord> finished = new ArrayList<>();
    boolean sms = false;
    boolean smsFailed = false;
    String messageId = providerExtraData.get(EbodacConstants.MESSAGE_ID);
    DateTime sendDate =
        DateTime.parse(initialRecord.getMotechTimestamp(), motechTimestampFormatter);
    int attempts = 0;
    DateTime receivedDate = null;
    DateTime smsReceivedDate = null;
    double expectedDuration = 0;
    double timeListenedTo = 0;
    double messagePercentListened = 0;

    for (CallDetailRecord callDetailRecord : callDetailRecords) {
      if (callDetailRecord.getCallStatus() == null) {
        continue;
      }
      if (callDetailRecord
          .getCallStatus()
          .contains(EbodacConstants.IVR_CALL_DETAIL_RECORD_STATUS_SUBMITTED)) {
        sms = true;
      } else if (callDetailRecord
          .getCallStatus()
          .contains(EbodacConstants.IVR_CALL_DETAIL_RECORD_STATUS_FINISHED)) {
        finished.add(callDetailRecord);
      } else if (callDetailRecord
          .getCallStatus()
          .contains(EbodacConstants.IVR_CALL_DETAIL_RECORD_STATUS_FAILED)) {
        failed.add(callDetailRecord);
      }
    }

    Integer recordsCount = failed.size() + finished.size();
    CallDetailRecord callRecord;

    if (sms) {
      if (failed.isEmpty()) {
        throw new EbodacReportException(
            "Cannot generate report for Call Detail Record with Provider Call Id: %s for Providers with Ids %s, because SMS was sent but no failed record found for the Call",
            "", providerCallId, subjectIds);
      }
      if (recordsCount > 2) {
        throw new EbodacReportException(
            "Cannot generate report for Call Detail Record with Provider Call Id: %s for Providers with Ids %s, because there is too much records with failed/finished status (%s)",
            "", providerCallId, subjectIds, recordsCount.toString());
      }
      if (failed.size() == 2) {
        smsFailed = true;
        LOGGER.warn(
            "Failed to sent SMS for Call Detail Record with Provider Call Id: {} for Providers with Ids {}",
            providerCallId,
            subjectIds);
      } else if (finished.isEmpty()) {
        LOGGER.warn(
            "SMS is sent but not yet received for Call Detail Record with Provider Call Id: {} for Providers with Ids {}",
            providerCallId,
            subjectIds);
        Config config = configService.getConfig();
        config.getIvrAndSmsStatisticReportsToUpdate().add(initialRecord.getMotechCallId());
        configService.updateConfig(config);
      } else {
        String providerTimestamp =
            finished
                .get(0)
                .getProviderExtraData()
                .get(EbodacConstants.IVR_CALL_DETAIL_RECORD_END_TIMESTAMP);
        if (StringUtils.isBlank(providerTimestamp)) {
          throw new EbodacReportException(
              "Cannot generate report for Call Detail Record with Provider Call Id: %s for Providers with Ids %s, because end_timestamp for SMS Record not found",
              "", providerCallId, subjectIds);
        }
        smsReceivedDate = DateTime.parse(providerTimestamp, votoTimestampFormatter);
      }

      callRecord = failed.get(0);
    } else if (recordsCount == 2 && failed.size() == 2) {
      callRecord = failed.get(0);
      smsFailed = true;
      LOGGER.warn(
          "Failed to sent SMS for Call Detail Record with Provider Call Id: {} for Providers with Ids {}",
          providerCallId,
          subjectIds);
    } else {
      if (recordsCount > 1) {
        throw new EbodacReportException(
            "Cannot generate report for Call Detail Record with Provider Call Id: %s for Providers with Ids %s, because there is too much records with failed/finished status (%s)",
            "", providerCallId, subjectIds, recordsCount.toString());
      }
      if (finished.isEmpty()) {
        throw new EbodacReportException(
            "Cannot generate report for Call Detail Record with Provider Call Id: %s for Providers with Ids %s, because no SMS was sent but there is no record with finished status",
            "", providerCallId, subjectIds);
      }

      callRecord = finished.get(0);
      String providerTimestamp =
          callRecord
              .getProviderExtraData()
              .get(EbodacConstants.IVR_CALL_DETAIL_RECORD_START_TIMESTAMP);
      if (StringUtils.isBlank(providerTimestamp)) {
        throw new EbodacReportException(
            "Cannot generate report for Call Detail Record with Provider Call Id: %s for Providers with Ids %s, because start_timestamp for Call Record not found",
            "", providerCallId, subjectIds);
      }
      receivedDate = DateTime.parse(providerTimestamp, votoTimestampFormatter);

      if (StringUtils.isNotBlank(callRecord.getCallDuration())) {
        timeListenedTo = Double.parseDouble(callRecord.getCallDuration());
        if (StringUtils.isNotBlank(callRecord.getMessagePercentListened())) {
          messagePercentListened = Double.parseDouble(callRecord.getMessagePercentListened());
          String messageSecondsCompleted =
              callRecord
                  .getProviderExtraData()
                  .get(EbodacConstants.IVR_CALL_DETAIL_RECORD_MESSAGE_SECOND_COMPLETED);
          if (StringUtils.isNotBlank(messageSecondsCompleted)) {
            expectedDuration =
                Double.parseDouble(messageSecondsCompleted)
                    * HUNDRED_PERCENT
                    / messagePercentListened;
          }
        }
      }
    }

    String attemptsString =
        callRecord
            .getProviderExtraData()
            .get(EbodacConstants.IVR_CALL_DETAIL_RECORD_NUMBER_OF_ATTEMPTS);
    if (StringUtils.isNotBlank(attemptsString)) {
      attempts = Integer.parseInt(attemptsString);
    }

    for (Subject subject : subjects) {
      IvrAndSmsStatisticReport ivrAndSmsStatisticReport =
          ivrAndSmsStatisticReportDataService.findByProviderCallIdAndSubjectId(
              providerCallId, subject.getSubjectId());
      if (ivrAndSmsStatisticReport == null) {
        ivrAndSmsStatisticReport =
            new IvrAndSmsStatisticReport(
                providerCallId,
                subject,
                messageId,
                sendDate,
                expectedDuration,
                timeListenedTo,
                messagePercentListened,
                receivedDate,
                attempts,
                sms,
                smsFailed,
                smsReceivedDate);
        ivrAndSmsStatisticReportDataService.create(ivrAndSmsStatisticReport);
      } else {
        ivrAndSmsStatisticReport.updateReportData(
            providerCallId,
            subject,
            messageId,
            sendDate,
            expectedDuration,
            timeListenedTo,
            messagePercentListened,
            receivedDate,
            attempts,
            sms,
            smsFailed,
            smsReceivedDate);
        ivrAndSmsStatisticReportDataService.update(ivrAndSmsStatisticReport);
      }
    }
  }