@ApiOperation(
      value = "Get all details for a specified certified product.",
      notes = "Returns all information in the CHPL related to the specified certified product.")
  @RequestMapping(
      value = "/{certifiedProductId}/details",
      method = RequestMethod.GET,
      produces = "application/json; charset=utf-8")
  public @ResponseBody CertifiedProductSearchDetails getCertifiedProductById(
      @PathVariable("certifiedProductId") Long certifiedProductId) throws EntityRetrievalException {
    CertifiedProductSearchDetails certifiedProduct =
        cpdManager.getCertifiedProductDetails(certifiedProductId);
    CertifiedProductValidator validator = validatorFactory.getValidator(certifiedProduct);
    if (validator != null) {
      validator.validate(certifiedProduct);
    }

    return certifiedProduct;
  }
  @ApiOperation(
      value = "Confirm a pending certified product.",
      notes =
          "Creates a new certified product in the system based on all of the information "
              + " passed in on the request. This information may differ from what was previously "
              + " entered for the pending certified product during upload. It will first be validated "
              + " to check for errors, then a new certified product is created, and the old pending certified"
              + " product will be removed. ROLE_ACB_ADMIN or ROLE_ACB_STAFF "
              + " and administrative authority on the ACB is required.")
  @RequestMapping(
      value = "/pending/confirm",
      method = RequestMethod.POST,
      produces = "application/json; charset=utf-8")
  public @ResponseBody CertifiedProductSearchDetails confirmPendingCertifiedProduct(
      @RequestBody(required = true) PendingCertifiedProductDetails pendingCp)
      throws InvalidArgumentsException, ValidationException, EntityCreationException,
          EntityRetrievalException, JsonProcessingException {

    String acbIdStr = pendingCp.getCertifyingBody().get("id").toString();
    if (StringUtils.isEmpty(acbIdStr)) {
      throw new InvalidArgumentsException("An ACB ID must be supplied in the request body");
    }

    PendingCertifiedProductDTO pcpDto = new PendingCertifiedProductDTO(pendingCp);
    CertifiedProductValidator validator = validatorFactory.getValidator(pcpDto);
    if (validator != null) {
      validator.validate(pcpDto);
    }
    if (pcpDto.getErrorMessages() != null && pcpDto.getErrorMessages().size() > 0) {
      throw new ValidationException(pcpDto.getErrorMessages(), pcpDto.getWarningMessages());
    }

    Long acbId = new Long(acbIdStr);
    CertifiedProductDTO createdProduct = cpManager.createFromPending(acbId, pcpDto);
    pcpManager.confirm(pendingCp.getId());

    CertifiedProductSearchDetails result =
        cpdManager.getCertifiedProductDetails(createdProduct.getId());
    return result;
  }
  @ApiOperation(
      value = "Update an existing certified product.",
      notes =
          "Updates the certified product after first validating the request. The logged in"
              + " user must have ROLE_ADMIN or ROLE_ACB_ADMIN and have administrative "
              + " authority on the ACB that certified the product. If a different ACB is passed in"
              + " as part of the request, an ownership change will take place and the logged in "
              + " user must have ROLE_ADMIN.")
  @RequestMapping(
      value = "/update",
      method = RequestMethod.POST,
      produces = "application/json; charset=utf-8")
  public @ResponseBody CertifiedProductSearchDetails updateCertifiedProduct(
      @RequestBody(required = true) CertifiedProductSearchDetails updateRequest)
      throws EntityCreationException, EntityRetrievalException, InvalidArgumentsException,
          JsonProcessingException, ValidationException {

    // make sure the ui didn't send any error or warning messages back
    updateRequest.setErrorMessages(new HashSet<String>());
    updateRequest.setWarningMessages(new HashSet<String>());
    // validate
    CertifiedProductValidator validator = validatorFactory.getValidator(updateRequest);
    if (validator != null) {
      validator.validate(updateRequest);
    }
    if (updateRequest.getErrorMessages() != null && updateRequest.getErrorMessages().size() > 0) {
      throw new ValidationException(
          updateRequest.getErrorMessages(), updateRequest.getWarningMessages());
    }

    CertifiedProductSearchDetails existingProduct =
        cpdManager.getCertifiedProductDetails(updateRequest.getId());
    Long acbId = new Long(existingProduct.getCertifyingBody().get("id").toString());
    Long newAcbId = new Long(updateRequest.getCertifyingBody().get("id").toString());

    if (newAcbId != null && acbId.longValue() != newAcbId.longValue()) {
      cpManager.changeOwnership(updateRequest.getId(), newAcbId);
      CertifiedProductSearchDetails changedProduct =
          cpdManager.getCertifiedProductDetails(updateRequest.getId());
      activityManager.addActivity(
          ActivityConcept.ACTIVITY_CONCEPT_CERTIFIED_PRODUCT,
          existingProduct.getId(),
          "Changed ACB ownership.",
          existingProduct,
          changedProduct);
      existingProduct = changedProduct;
    }

    CertifiedProductDTO toUpdate = new CertifiedProductDTO();
    toUpdate.setId(updateRequest.getId());
    if (updateRequest.getTestingLab() != null
        && !StringUtils.isEmpty(updateRequest.getTestingLab().get("id"))) {
      toUpdate.setTestingLabId(new Long(updateRequest.getTestingLab().get("id").toString()));
    }
    toUpdate.setCertificationBodyId(newAcbId);
    if (updateRequest.getPracticeType() != null
        && updateRequest.getPracticeType().get("id") != null) {
      toUpdate.setPracticeTypeId(new Long(updateRequest.getPracticeType().get("id").toString()));
    }
    if (updateRequest.getClassificationType() != null
        && updateRequest.getClassificationType().get("id") != null) {
      toUpdate.setProductClassificationTypeId(
          new Long(updateRequest.getClassificationType().get("id").toString()));
    }
    toUpdate.setProductVersionId(new Long(updateRequest.getProduct().get("versionId").toString()));
    toUpdate.setCertificationStatusId(
        new Long(updateRequest.getCertificationStatus().get("id").toString()));
    toUpdate.setCertificationEditionId(
        new Long(updateRequest.getCertificationEdition().get("id").toString()));
    toUpdate.setReportFileLocation(updateRequest.getReportFileLocation());
    toUpdate.setSedReportFileLocation(updateRequest.getSedReportFileLocation());
    toUpdate.setSedIntendedUserDescription(updateRequest.getSedIntendedUserDescription());
    toUpdate.setSedTestingEnd(updateRequest.getSedTestingEnd());
    toUpdate.setAcbCertificationId(updateRequest.getAcbCertificationId());
    toUpdate.setOtherAcb(updateRequest.getOtherAcb());
    toUpdate.setVisibleOnChpl(updateRequest.getVisibleOnChpl());
    toUpdate.setTermsOfUse(updateRequest.getTermsOfUse());
    toUpdate.setIcs(updateRequest.getIcs());
    toUpdate.setAccessibilityCertified(updateRequest.getAccessibilityCertified());
    toUpdate.setProductAdditionalSoftware(updateRequest.getProductAdditionalSoftware());

    toUpdate.setTransparencyAttestationUrl(updateRequest.getTransparencyAttestationUrl());

    // set the pieces of the unique id
    if (!StringUtils.isEmpty(updateRequest.getChplProductNumber())) {
      if (updateRequest.getChplProductNumber().startsWith("CHP-")) {
        toUpdate.setChplProductNumber(updateRequest.getChplProductNumber());
      } else {
        String chplProductId = updateRequest.getChplProductNumber();
        String[] chplProductIdComponents = chplProductId.split("\\.");
        if (chplProductIdComponents == null || chplProductIdComponents.length != 9) {
          throw new InvalidArgumentsException(
              "CHPL Product Id " + chplProductId + " is not in a format recognized by the system.");
        } else {
          toUpdate.setProductCode(chplProductIdComponents[4]);
          toUpdate.setVersionCode(chplProductIdComponents[5]);
          toUpdate.setIcsCode(chplProductIdComponents[6]);
          toUpdate.setAdditionalSoftwareCode(chplProductIdComponents[7]);
          toUpdate.setCertifiedDateCode(chplProductIdComponents[8]);
        }

        if (updateRequest.getCertificationDate() != null) {
          Date certDate = new Date(updateRequest.getCertificationDate());
          SimpleDateFormat dateCodeFormat = new SimpleDateFormat("yyMMdd");
          String dateCode = dateCodeFormat.format(certDate);
          toUpdate.setCertifiedDateCode(dateCode);
        }

        if (updateRequest.getCertificationResults() != null
            && updateRequest.getCertificationResults().size() > 0) {
          boolean hasSoftware = false;
          for (CertificationResult cert : updateRequest.getCertificationResults()) {
            if (cert.getAdditionalSoftware() != null && cert.getAdditionalSoftware().size() > 0) {
              hasSoftware = true;
            }
          }
          if (hasSoftware) {
            toUpdate.setAdditionalSoftwareCode("1");
          } else {
            toUpdate.setAdditionalSoftwareCode("0");
          }
        }
      }
    }

    toUpdate = cpManager.update(acbId, toUpdate);

    // update qms standards used
    List<CertifiedProductQmsStandardDTO> qmsStandardsToUpdate =
        new ArrayList<CertifiedProductQmsStandardDTO>();
    for (CertifiedProductQmsStandard newQms : updateRequest.getQmsStandards()) {
      CertifiedProductQmsStandardDTO dto = new CertifiedProductQmsStandardDTO();
      dto.setId(newQms.getId());
      dto.setApplicableCriteria(newQms.getApplicableCriteria());
      dto.setCertifiedProductId(toUpdate.getId());
      dto.setQmsModification(newQms.getQmsModification());
      dto.setQmsStandardId(newQms.getQmsStandardId());
      dto.setQmsStandardName(newQms.getQmsStandardName());
      qmsStandardsToUpdate.add(dto);
    }
    cpManager.updateQmsStandards(acbId, toUpdate, qmsStandardsToUpdate);

    // update targeted users
    List<CertifiedProductTargetedUserDTO> targetedUsersToUpdate =
        new ArrayList<CertifiedProductTargetedUserDTO>();
    for (CertifiedProductTargetedUser newTu : updateRequest.getTargetedUsers()) {
      CertifiedProductTargetedUserDTO dto = new CertifiedProductTargetedUserDTO();
      dto.setId(newTu.getId());
      dto.setCertifiedProductId(toUpdate.getId());
      dto.setTargetedUserId(newTu.getTargetedUserId());
      dto.setTargetedUserName(newTu.getTargetedUserName());
      targetedUsersToUpdate.add(dto);
    }
    cpManager.updateTargetedUsers(acbId, toUpdate, targetedUsersToUpdate);

    // update accessibility standards
    List<CertifiedProductAccessibilityStandardDTO> accessibilityStandardsToUpdate =
        new ArrayList<CertifiedProductAccessibilityStandardDTO>();
    for (CertifiedProductAccessibilityStandard newStd : updateRequest.getAccessibilityStandards()) {
      CertifiedProductAccessibilityStandardDTO dto = new CertifiedProductAccessibilityStandardDTO();
      dto.setId(newStd.getId());
      dto.setCertifiedProductId(toUpdate.getId());
      dto.setAccessibilityStandardId(newStd.getAccessibilityStandardId());
      dto.setAccessibilityStandardName(newStd.getAccessibilityStandardName());
      accessibilityStandardsToUpdate.add(dto);
    }
    cpManager.updateAccessibilityStandards(acbId, toUpdate, accessibilityStandardsToUpdate);

    // update certification date
    cpManager.updateCertificationDate(
        acbId, toUpdate, new Date(updateRequest.getCertificationDate()));

    // update product certifications
    cpManager.updateCertifications(acbId, toUpdate, updateRequest.getCertificationResults());

    // update CQMs
    List<CQMResultDetailsDTO> cqmDtos = new ArrayList<CQMResultDetailsDTO>();
    for (CQMResultDetails cqm : updateRequest.getCqmResults()) {
      if (!StringUtils.isEmpty(cqm.getCmsId())
          && cqm.getSuccessVersions() != null
          && cqm.getSuccessVersions().size() > 0) {
        for (String version : cqm.getSuccessVersions()) {
          CQMResultDetailsDTO cqmDto = new CQMResultDetailsDTO();
          cqmDto.setNqfNumber(cqm.getNqfNumber());
          cqmDto.setCmsId(cqm.getCmsId());
          cqmDto.setNumber(cqm.getNumber());
          cqmDto.setCmsId(cqm.getCmsId());
          cqmDto.setNqfNumber(cqm.getNqfNumber());
          cqmDto.setTitle(cqm.getTitle());
          cqmDto.setVersion(version);
          cqmDto.setSuccess(Boolean.TRUE);
          if (cqm.getCriteria() != null && cqm.getCriteria().size() > 0) {
            for (CQMResultCertification criteria : cqm.getCriteria()) {
              CQMResultCriteriaDTO dto = new CQMResultCriteriaDTO();
              dto.setCriterionId(criteria.getCertificationId());
              CertificationCriterionDTO certDto = new CertificationCriterionDTO();
              certDto.setNumber(criteria.getCertificationNumber());
              dto.setCriterion(certDto);
              cqmDto.getCriteria().add(dto);
            }
          }
          cqmDtos.add(cqmDto);
        }
      } else if (StringUtils.isEmpty(cqm.getCmsId())) {
        CQMResultDetailsDTO cqmDto = new CQMResultDetailsDTO();
        cqmDto.setNqfNumber(cqm.getNqfNumber());
        cqmDto.setCmsId(cqm.getCmsId());
        cqmDto.setNumber(cqm.getNumber());
        cqmDto.setCmsId(cqm.getCmsId());
        cqmDto.setNqfNumber(cqm.getNqfNumber());
        cqmDto.setTitle(cqm.getTitle());
        cqmDto.setSuccess(cqm.isSuccess());
        cqmDtos.add(cqmDto);
      }
    }
    cpManager.updateCqms(acbId, toUpdate, cqmDtos);

    CertifiedProductSearchDetails changedProduct =
        cpdManager.getCertifiedProductDetails(updateRequest.getId());
    cpManager.checkSuspiciousActivity(existingProduct, changedProduct);
    activityManager.addActivity(
        ActivityConcept.ACTIVITY_CONCEPT_CERTIFIED_PRODUCT,
        existingProduct.getId(),
        "Updated certified product " + changedProduct.getChplProductNumber() + ".",
        existingProduct,
        changedProduct);

    // search for the product by id to get it with all the updates
    return changedProduct;
  }