GlisSMTAReportingManager.java

/*
 * Copyright 2022 Global Crop Diversity Trust
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.gringlobal.service.glis.impl;

import static org.gringlobal.model.community.CommunityCodeValues.ORDER_REQUEST_ACTION_REPORT_TO_ITPGRFA;

import java.io.ByteArrayOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.genesys.glis.v1.api.ManagerApi;
import org.genesys.glis.v1.model.SMTAActorType;
import org.genesys.glis.v1.model.SMTADocumentLocation;
import org.genesys.glis.v1.model.SMTADocumentPDFRequest;
import org.genesys.glis.v1.model.SMTADocumentPDFRequestRecipient;
import org.genesys.glis.v1.model.SMTAMaterial;
import org.genesys.glis.v1.model.SMTAMaterialUnderDevelopment;
import org.genesys.glis.v1.model.SMTAReport;
import org.genesys.glis.v1.model.SMTAReportDocument;
import org.genesys.glis.v1.model.SMTAReportProvider;
import org.genesys.glis.v1.model.SMTAReportRecipient;
import org.genesys.glis.v1.model.SMTAReportResponseError;
import org.genesys.glis.v1.model.SMTASignatureType;
import org.gringlobal.api.exception.InvalidApiUsageException;
import org.gringlobal.api.exception.NotFoundElement;
import org.gringlobal.model.AbstractAction;
import org.gringlobal.model.Accession;
import org.gringlobal.model.Cooperator;
import org.gringlobal.model.OrderRequest;
import org.gringlobal.model.OrderRequestItem;
import org.gringlobal.service.AppSettingsService;
import org.gringlobal.service.OrderRequestActionService;
import org.gringlobal.service.filter.OrderRequestActionFilter;
import org.gringlobal.service.filter.OrderRequestFilter;
import org.hibernate.Hibernate;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
@Slf4j
public class GlisSMTAReportingManager {

	private static final String APPSETTINGS_ITPGRFA_EASYSMTA = "ITPGRFA_EASYSMTA";
	private static final String DOI_PREFIX = "doi:";

	public static final String RESPONSE_SUCCESS = "OK";
	public static final String RESPONSE_ERROR = "KO";

	static final String SETTING_PID = "pid";
	static final String SETTING_PASSWORD = "password";
	static final String SETTING_USERNAME = "username";
	static final String SETTING_INSTITUTE_NAME = "instituteName";
	static final String SETTING_INSTITUTE_ADDRESS = "instituteAddress";
	static final String SETTING_INSTITUTE_COUNTRY_CODE = "instituteCountryCode";
	static final String SETTING_SMTA_RETRIEVAL_INFO = "smtaRetrievalInfo";

	private final SimpleDateFormat requestFormat = new SimpleDateFormat("yyyy-MM-dd");
	private final JAXBContext context;

	@Autowired
	private AppSettingsService appSettingsService;
	@Autowired
	private OrderRequestActionService orderRequestActionService;

	@Autowired
	@Qualifier("glisSMTAManagerApiFactory")
	private FactoryBean<ManagerApi> managerApiFactory;

	public GlisSMTAReportingManager() throws JAXBException {
		context = JAXBContext.newInstance(SMTAReport.class, SMTADocumentPDFRequest.class);
	}

	@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
	@Transactional(readOnly = true)
	public GlisSMTADocumentPDFResponse generateSMTA(OrderRequest orderRequest) throws Exception {
		var managerApi = createGlisManager();
		if (managerApi == null) {
			throw new InvalidApiUsageException("Easy-SMTA client not available.");
		}

		if (log.isDebugEnabled() || log.isTraceEnabled()) {
			managerApi.getApiClient().setDebugging(true);
		}

		String glisUsername = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_USERNAME).getValue();
		String glisPassword = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_PASSWORD).getValue();

		SMTADocumentPDFRequest smtaRequest = createDocumentPDFRequest(orderRequest);

		var marshaller = context.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		marshaller.marshal(smtaRequest, outputStream);

		try {
			var response = managerApi.downloadSMTADocument("extsys/getpdf", glisUsername, glisPassword, outputStream.toByteArray());
			var result = response.getResult();
			if ("KO".equals(result)) {
				var errors = response.getErrors();
				var errorMessages = errors == null ? null : errors.stream().map(SMTAReportResponseError::getMsg).collect(Collectors.toList());
				return new GlisSMTADocumentPDFResponse(RESPONSE_ERROR, errorMessages);
			} else {
				return new GlisSMTADocumentPDFResponse(RESPONSE_SUCCESS, response.getPdf());
			}
		} catch (Throwable e) {
			log.error("Error interacting with GLIS SMTA API {}: {}", e.getClass(), e.getMessage());
			return new GlisSMTADocumentPDFResponse(RESPONSE_ERROR, List.of(e.getMessage()));
		}
	}

	private SMTADocumentPDFRequest createDocumentPDFRequest(OrderRequest orderRequest) {
		String glisInstitutePid = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_PID).getValue();
		
		var recipientCooperator = (Cooperator) Hibernate.unproxy(orderRequest.getFinalRecipientCooperator());
		SMTADocumentPDFRequestRecipient recipient = new SMTADocumentPDFRequestRecipient()
			.type(recipientCooperator.getLastName() == null ? SMTAActorType.OR : SMTAActorType.IN)
			.pid(recipientCooperator.getItpgrfaPid())
			.name(recipientCooperator.getFirstName())
			.address(recipientCooperator.getAddressLine1())
			.country(recipientCooperator.getGeography() != null ? recipientCooperator.getGeography().getCountryCode() : null);
//			TODO
//			.aoname()
//			.aoemail()
//			.aofax()
//			.aophone()

		String language = "en";
		String tag = recipientCooperator.getSysLang().getIso6393Tag();
		switch (tag) {
			case "SPA":
				language = "es";
				break;
			case "FRA":
				language = "fr";
				break;
			case "ARA":
				language = "ar";
				break;
			case "RUS":
				language = "ru";
				break;
		}

		return new SMTADocumentPDFRequest()
			.symbol(orderRequest.getLocalNumber() != null ? orderRequest.getLocalNumber() : "GGCE-OR-" + orderRequest.getId())
			.date(orderRequest.getCompletedDate() != null ? requestFormat.format(orderRequest.getCompletedDate()) : requestFormat.format(new Date())) // Use now if completed date is missing
			.type(getSMTAType(orderRequest)) //  orderRequest.getWebOrderRequest() != null ? SMTASignatureType.SW : SMTASignatureType.CW; // We're not considering web order request
			.language(language)
			.providerPID(glisInstitutePid)
			.recipient(recipient)
			.annex1(orderRequest.getOrderRequestItems().stream().map(this::reportXmlMaterial).distinct().collect(Collectors.toList()));
	}

	private SMTASignatureType getSMTAType(OrderRequest orderRequest) {
		switch (orderRequest.getMtaType()) {
			case "SMTA_SHRINK": return SMTASignatureType.SW;
			case "SMTA_CLICK": return SMTASignatureType.CW;
			case "SMTA_SIGNED":
			default:
			 return SMTASignatureType.CW;
		}
	}

	public static class GlisSMTADocumentPDFResponse {
		public String status;
		public List<String> errors;
		public String pdf;

		public GlisSMTADocumentPDFResponse(String status, List<String> errors) {
			this.status = status;
			this.errors = errors;
		}

		public GlisSMTADocumentPDFResponse(String status, String pdf) {
			this.status = status;
			this.pdf = pdf;
		}
	}

	@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
	@Transactional(readOnly = true)
	public GlisSMTAReportResponse uploadOrderRequestReport(OrderRequest orderRequest) throws Exception {
		var managerApi = createGlisManager();
		if (managerApi == null) {
			throw new InvalidApiUsageException("Easy-SMTA client not available.");
		}

		if (log.isDebugEnabled() || log.isTraceEnabled()) {
			managerApi.getApiClient().setDebugging(true);
		}
		String glisUsername = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_USERNAME).getValue();
		String glisPassword = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_PASSWORD).getValue();

		var smtaReportXml = createReport(orderRequest);

		var marshaller = context.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		marshaller.marshal(smtaReportXml, outputStream);

		try {
			var response = managerApi.uploadReport("extsys/uploadxml", glisUsername, glisPassword, outputStream.toByteArray(), null);
			var errors = response.getError();
			if (CollectionUtils.isNotEmpty(errors)) {
				return new GlisSMTAReportResponse(RESPONSE_ERROR, errors.stream().map(SMTAReportResponseError::getMsg).collect(Collectors.toList()));
			} else {
				completeSMTAReportAction(orderRequest);
				return new GlisSMTAReportResponse(RESPONSE_SUCCESS, null);
			}
		} catch (Throwable e) {
			log.error("Error interacting with GLIS SMTA API {}: {}", e.getClass(), e.getMessage());
			return new GlisSMTAReportResponse(RESPONSE_ERROR, List.of(e.getMessage()));
		}
	}

	private void completeSMTAReportAction(OrderRequest orderRequest) {
		var filter = new OrderRequestActionFilter();
		filter.actionNameCode(Set.of(ORDER_REQUEST_ACTION_REPORT_TO_ITPGRFA.value));
		filter
			.orderRequest((OrderRequestFilter) new OrderRequestFilter().id(Set.of(orderRequest.getId())))
			.states(Set.of(AbstractAction.ActionState.INPROGRESS));
		var actions = orderRequestActionService.listActions(filter, Pageable.unpaged()).getContent();
		var actionRequest = OrderRequestActionService.OrderRequestActionRequest.builder()
			.actionNameCode(ORDER_REQUEST_ACTION_REPORT_TO_ITPGRFA.value)
			.id(Set.of(orderRequest.getId()))
			.build();
		if (actions.isEmpty()) {
			orderRequestActionService.startAction(actionRequest);
		}
		orderRequestActionService.completeAction(actionRequest);
	}

	public static class GlisSMTAReportResponse {
		public String status;
		public List<String> errors;

		public GlisSMTAReportResponse(String status, List<String> errors) {
			this.status = status;
			this.errors = errors;
		}
	}

	private SMTAReport createReport(OrderRequest orderRequest) {
		String glisInstitutePid = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_PID).getValue();
		String glisInstituteName = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_INSTITUTE_NAME).getValue();
		String glisInstituteAddress = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_INSTITUTE_ADDRESS).getValue();
		String glisInstituteCountry = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_INSTITUTE_COUNTRY_CODE).getValue();
		String providerEmail = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, "instituteEmail").getValue();

//		Long smtaProviderCoopId = appSettingRepository.findOne(QAppSetting.appSetting.name.eq("providerCooperatorId"))
//			.map(s -> Long.valueOf(s.getValue())).orElse(null);
//		if (smtaProviderCoopId != null) {
//			var smtaProviderCoop = cooperatorRepository.findById(smtaProviderCoopId).orElse(null);
//			providerEmail = smtaProviderCoop != null ? smtaProviderCoop.getEmail() : null;
//		}
		SMTAReportProvider provider = new SMTAReportProvider()
			.type(SMTAActorType.OR)
			.pid(glisInstitutePid)
			.name(glisInstituteName)
			.address(glisInstituteAddress)
			.country(glisInstituteCountry)
			.email(providerEmail);

		var recipientCooperator = (Cooperator) Hibernate.unproxy(orderRequest.getFinalRecipientCooperator());
		SMTAReportRecipient recipient = new SMTAReportRecipient()
			.type(recipientCooperator.getLastName() == null ? SMTAActorType.OR : SMTAActorType.IN)
			.pid(recipientCooperator.getItpgrfaPid())
			.name(recipientCooperator.getFirstName())
			.address(recipientCooperator.getAddressLine1())
			.country(recipientCooperator.getGeography() != null ? recipientCooperator.getGeography().getCountryCode() : null);

		// TODO Figure out which document to attach
//		String pdf = null;
//		try {
//			var attach = orderRequestAttachRepository.findOne(QOrderRequestAttach.orderRequestAttach.orderRequest.id.eq(orderRequest.getId())).orElse(null);
//			if (attach != null) {
//				var bytes = repositoryService.getFileBytes(attach.getRepositoryFile());
//				if (bytes != null) {
//					pdf = Base64.encodeBase64String(bytes);
//				}
//			}
//		} catch (IOException e) {
//			LOG.warn("Exception in getting order request attach", e);
//		}
		
		SMTAReportDocument document = new SMTAReportDocument()
			.location(SMTADocumentLocation.P)
			.retInfo(appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_SMTA_RETRIEVAL_INFO, String.class).orElse(null))
			.pdf(null);

		String language = "en";
		String tag = recipientCooperator.getSysLang().getIso6393Tag();
		switch (tag) {
			case "SPA":
				language = "es";
				break;
			case "FRA":
				language = "fr";
				break;
			case "ARA":
				language = "ar";
				break;
			case "RUS":
				language = "ru";
				break;
		}

		String shipName;
		if (recipientCooperator.getLastName() == null) {
			String org = recipientCooperator.getOrganization();
			if (org == null) {
				org = recipientCooperator.getOrganizationAbbrev();
			}
			shipName = org;
		} else {
			shipName = recipientCooperator.getFirstName() + " " + recipientCooperator.getLastName();
		}
		shipName = shipName != null ? shipName : "UNKNOWN";

		return new SMTAReport()
			.symbol(orderRequest.getLocalNumber() != null ? orderRequest.getLocalNumber() : "GGCE-OR-" + orderRequest.getId())
			.date(orderRequest.getCompletedDate() != null ? requestFormat.format(orderRequest.getCompletedDate()) : null)
			.type(getSMTAType(orderRequest)) //  orderRequest.getWebOrderRequest() != null ? SMTASignatureType.SW : SMTASignatureType.CW; // We're not considering web order request
			.language(language)
			.shipName(shipName)
			.provider(provider)
			.recipient(recipient)
			.annex1(orderRequest.getOrderRequestItems().stream().map(this::reportXmlMaterial).distinct().collect(Collectors.toList()))
			.document(document);
	}

	private SMTAMaterial reportXmlMaterial(OrderRequestItem item) {
		Accession accession = (Accession) Hibernate.unproxy(item.getInventory().getAccession());
		return new SMTAMaterial()
			.crop(accession.getTaxonomySpecies().reportCropName())
			.sampleID(accession.getDoi() != null ? DOI_PREFIX + accession.getDoi() : accession.getAccessionNumber())
			.pud(SMTAMaterialUnderDevelopment.N)
			.ancestry(accession.getAccessionPedigree() != null ? accession.getAccessionPedigree().getDescription() : null);
	}

	private ManagerApi createGlisManager() throws Exception {
		var managerApi = managerApiFactory.getObject();
		if (managerApi == null) {
			throw new InvalidApiUsageException("Easy-SMTA client not available.");
		}

		if (log.isDebugEnabled() || log.isTraceEnabled()) {
			managerApi.getApiClient().setDebugging(true);
		}

		try {
			String glisUsername = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_USERNAME).getValue();
			appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_PASSWORD).getValue();
			String glisInstitutePid = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_PID).getValue();
			appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_INSTITUTE_NAME).getValue();
			appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_INSTITUTE_ADDRESS).getValue();
			appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_INSTITUTE_COUNTRY_CODE).getValue();

			if (StringUtils.isBlank(glisUsername) || StringUtils.isBlank(glisInstitutePid)) {
				throw new InvalidApiUsageException("Missing credentials for Easy-SMTA");
			}
			return managerApi;

		} catch (NotFoundElement e) {
			throw new InvalidApiUsageException("Easy-SMTA configuration is incomplete", e);
		}
	}
}