package org.brapi.test.BrAPITestServer.service.geno;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerDbIdNotFoundException;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException;
import org.brapi.test.BrAPITestServer.model.dto.geno.ReferenceSetWriteRequest;
import org.brapi.test.BrAPITestServer.model.entity.germ.GermplasmEntity;
import org.brapi.test.BrAPITestServer.model.entity.geno.ReferenceEntity;
import org.brapi.test.BrAPITestServer.model.entity.geno.ReferenceSetEntity;
import org.brapi.test.BrAPITestServer.model.entity.geno.VariantEntity;
import org.brapi.test.BrAPITestServer.model.entity.geno.VariantSetEntity;
import org.brapi.test.BrAPITestServer.repository.geno.ReferenceRepository;
import org.brapi.test.BrAPITestServer.repository.geno.ReferenceSetRepository;
import org.brapi.test.BrAPITestServer.repository.geno.VariantRepository;
import org.brapi.test.BrAPITestServer.repository.geno.VariantSetRepository;
import org.brapi.test.BrAPITestServer.service.germ.GermplasmService;
import org.brapi.test.BrAPITestServer.service.PagingUtility;
import org.brapi.test.BrAPITestServer.service.SearchQueryBuilder;
import org.brapi.test.BrAPITestServer.service.UpdateUtility;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

import io.swagger.model.Metadata;
import io.swagger.model.geno.OntologyTerm;
import io.swagger.model.geno.ReferenceSet;
import io.swagger.model.geno.ReferenceSetsSearchRequest;
import io.swagger.model.geno.ReferenceSourceGermplasm;

@Service
public class ReferenceSetService {

	private final ReferenceSetRepository referenceSetRepository;
	private final ReferenceRepository referenceRepository;
	private final VariantSetRepository variantSetRepository;
	private final VariantRepository variantRepository;
	private final GermplasmService germplasmService;

	public ReferenceSetService(ReferenceSetRepository referenceSetRepository, ReferenceRepository referenceRepository,
			VariantSetRepository variantSetRepository, VariantRepository variantRepository,
			GermplasmService germplasmService) {
		this.referenceSetRepository = referenceSetRepository;
		this.referenceRepository = referenceRepository;
		this.variantSetRepository = variantSetRepository;
		this.variantRepository = variantRepository;
		this.germplasmService = germplasmService;
	}

	public List<ReferenceSet> findReferenceSets(String referenceSetDbId, String accession, String assemblyPUI,
			String md5checksum, String trialDbId, String studyDbId, String commonCropName, String programDbId,
			String externalReferenceId, String externalReferenceSource, Metadata metadata) {
		ReferenceSetsSearchRequest request = new ReferenceSetsSearchRequest();
		if (referenceSetDbId != null)
			request.addReferenceSetDbIdsItem(referenceSetDbId);
		if (accession != null)
			request.addAccessionsItem(accession);
		if (assemblyPUI != null)
			request.addAssemblyPUIsItem(assemblyPUI);
		if (md5checksum != null)
			request.addMd5checksumsItem(md5checksum);
		if (commonCropName != null)
			request.addCommonCropNamesItem(commonCropName);
		if (programDbId != null)
			request.addProgramDbIdsItem(programDbId);
		if (trialDbId != null)
			request.addTrialDbIdsItem(trialDbId);
		if (studyDbId != null)
			request.addStudyDbIdsItem(studyDbId);

		request.addExternalReferenceItem(externalReferenceId, null, externalReferenceSource);

		return findReferenceSets(request, metadata);
	}

	public List<ReferenceSet> findReferenceSets(ReferenceSetsSearchRequest request, Metadata metadata) {
		Pageable pageReq = PagingUtility.getPageRequest(metadata);
		SearchQueryBuilder<ReferenceSetEntity> searchQuery = new SearchQueryBuilder<ReferenceSetEntity>(
				ReferenceSetEntity.class).appendList(request.getAccessions(), "sourceGermplasm.accessionNumber")
						.appendList(request.getAssemblyPUIs(), "assemblyPUI")
						.appendList(request.getMd5checksums(), "md5checksum")
						.appendList(request.getReferenceSetDbIds(), "id");

		Page<ReferenceSetEntity> page = referenceSetRepository.findAllBySearch(searchQuery, pageReq);
		List<ReferenceSet> referenceSets = page.map(this::convertFromEntity).getContent();
		PagingUtility.calculateMetaData(metadata, page);
		return referenceSets;
	}

	public ReferenceSet getReferenceSet(String referenceSetDbId) throws BrAPIServerException {
		return convertFromEntity(getReferenceSetEntity(referenceSetDbId, HttpStatus.NOT_FOUND));
	}

	public ReferenceSetEntity getReferenceSetEntity(String referenceSetDbId) throws BrAPIServerException {
		return getReferenceSetEntity(referenceSetDbId, HttpStatus.BAD_REQUEST);
	}

	public ReferenceSetEntity getReferenceSetEntity(String referenceSetDbId, HttpStatus errorStatus)
			throws BrAPIServerException {
		ReferenceSetEntity referenceSet = null;
		Optional<ReferenceSetEntity> entityOpt = referenceSetRepository.findById(referenceSetDbId);
		if (entityOpt.isPresent()) {
			referenceSet = entityOpt.get();
		} else {
			throw new BrAPIServerDbIdNotFoundException("referenceSet", referenceSetDbId, errorStatus);
		}
		return referenceSet;
	}

	private ReferenceSet convertFromEntity(ReferenceSetEntity entity) {
		ReferenceSet refSet = new ReferenceSet();
		UpdateUtility.convertFromEntity(entity, refSet);

		refSet.setAssemblyPUI(entity.getAssemblyPUI());
		refSet.setDescription(entity.getDescription());
		refSet.setIsDerived(entity.getIsDerived());
		refSet.setMd5checksum(entity.getMd5checksum());
		refSet.setReferenceSetDbId(entity.getId());
		refSet.setReferenceSetName(entity.getReferenceSetName());
		refSet.setSourceURI(entity.getSourceURI());

		OntologyTerm term = new OntologyTerm().term(entity.getSpeciesOntologyTerm())
				.termURI(entity.getSpeciesOntologyTermURI());
		refSet.setSpecies(term);

		if (entity.getSourceGermplasm() != null && entity.getSourceGermplasm().getAccessionNumber() != null)
			refSet.setSourceAccessions(Arrays.asList(entity.getSourceGermplasm().getAccessionNumber()));

		if (entity.getSourceGermplasm() != null) {
			if (entity.getSourceGermplasm().getAccessionNumber() != null) {
				refSet.setSourceAccessions(Arrays.asList(entity.getSourceGermplasm().getAccessionNumber()));
			}
			refSet.setCommonCropName(entity.getSourceGermplasm().getCrop().getCropName());
			ReferenceSourceGermplasm sourceGerm = new ReferenceSourceGermplasm();
			sourceGerm.setGermplasmDbId(entity.getSourceGermplasm().getId());
			sourceGerm.setGermplasmName(entity.getSourceGermplasm().getGermplasmName());
			refSet.setSourceGermplasm(Arrays.asList(sourceGerm));
		}

		return refSet;
	}

	public ReferenceSet saveReferenceSet(ReferenceSetWriteRequest request) throws BrAPIServerException {
		if (request.getReferenceSetName() == null || request.getReferenceSetName().isBlank()) {
			throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "referenceSetName is required");
		}
		if (request.getReferenceSetDbId() != null && referenceSetRepository.findById(request.getReferenceSetDbId()).isPresent()) {
			throw new BrAPIServerException(HttpStatus.CONFLICT, "ReferenceSet already exists: " + request.getReferenceSetDbId());
		}
		ReferenceSetEntity entity = new ReferenceSetEntity();
		if (request.getReferenceSetDbId() != null && !request.getReferenceSetDbId().isBlank()) {
			entity.setId(request.getReferenceSetDbId().trim());
		}
		updateEntity(entity, request);
		return convertFromEntity(referenceSetRepository.save(entity));
	}

	public ReferenceSet updateReferenceSet(String referenceSetDbId, ReferenceSetWriteRequest request)
			throws BrAPIServerException {
		ReferenceSetEntity entity = getReferenceSetEntity(referenceSetDbId, HttpStatus.NOT_FOUND);
		updateEntity(entity, request);
		return convertFromEntity(referenceSetRepository.save(entity));
	}

	public ReferenceSet deleteReferenceSet(String referenceSetDbId) throws BrAPIServerException {
		ReferenceSetEntity entity = getReferenceSetEntity(referenceSetDbId, HttpStatus.NOT_FOUND);
		assertNoReferenceSetDependencies(referenceSetDbId);
		ReferenceSet deleted = convertFromEntity(entity);
		try {
			referenceSetRepository.delete(entity);
			referenceSetRepository.flush();
		} catch (DataIntegrityViolationException e) {
			throw new BrAPIServerException(HttpStatus.CONFLICT, "ReferenceSet is in use and cannot be deleted");
		}
		return deleted;
	}

	private void assertNoReferenceSetDependencies(String referenceSetDbId) throws BrAPIServerException {
		Pageable pageReq = PageRequest.of(0, 1);
		SearchQueryBuilder<ReferenceEntity> referenceQuery = new SearchQueryBuilder<ReferenceEntity>(ReferenceEntity.class)
				.appendSingle(referenceSetDbId, "referenceSet.id");
		if (referenceRepository.findAllBySearch(referenceQuery, pageReq).getTotalElements() > 0) {
			throw new BrAPIServerException(HttpStatus.CONFLICT, "ReferenceSet is referenced by Reference records");
		}
		SearchQueryBuilder<VariantSetEntity> variantSetQuery = new SearchQueryBuilder<VariantSetEntity>(VariantSetEntity.class)
				.appendSingle(referenceSetDbId, "referenceSet.id");
		if (variantSetRepository.findAllBySearch(variantSetQuery, pageReq).getTotalElements() > 0) {
			throw new BrAPIServerException(HttpStatus.CONFLICT, "ReferenceSet is referenced by VariantSet records");
		}
		SearchQueryBuilder<VariantEntity> variantQuery = new SearchQueryBuilder<VariantEntity>(VariantEntity.class)
				.appendSingle(referenceSetDbId, "referenceSet.id");
		if (variantRepository.findAllBySearch(variantQuery, pageReq).getTotalElements() > 0) {
			throw new BrAPIServerException(HttpStatus.CONFLICT, "ReferenceSet is referenced by Variant records");
		}
	}

	private void updateEntity(ReferenceSetEntity entity, ReferenceSetWriteRequest request) throws BrAPIServerException {
		entity.setReferenceSetName(request.getReferenceSetName().trim());
		entity.setAssemblyPUI(request.getAssemblyPUI());
		entity.setDescription(request.getDescription());
		entity.setIsDerived(request.getIsDerived());
		entity.setMd5checksum(request.getMd5checksum());
		entity.setSourceURI(request.getSourceURI());
		if (request.getSpecies() != null) {
			entity.setSpeciesOntologyTerm(request.getSpecies().getTerm());
			entity.setSpeciesOntologyTermURI(request.getSpecies().getTermURI());
		}
		if (request.getSourceGermplasmDbId() != null && !request.getSourceGermplasmDbId().isBlank()) {
			GermplasmEntity germplasm = germplasmService.getGermplasmEntity(request.getSourceGermplasmDbId());
			entity.setSourceGermplasm(germplasm);
		} else if (request.getSourceGermplasmDbId() != null) {
			entity.setSourceGermplasm(null);
		}
	}

}
