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

import java.util.List;

import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerDbIdNotFoundException;
import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException;
import org.brapi.test.BrAPITestServer.model.dto.geno.MarkerPositionWriteRequest;
import org.brapi.test.BrAPITestServer.model.entity.geno.LinkageGroupEntity;
import org.brapi.test.BrAPITestServer.model.entity.geno.MarkerPositionEntity;
import org.brapi.test.BrAPITestServer.model.entity.geno.VariantEntity;
import org.brapi.test.BrAPITestServer.repository.geno.MarkerPositionRepository;
import org.brapi.test.BrAPITestServer.service.PagingUtility;
import org.brapi.test.BrAPITestServer.service.SearchQueryBuilder;
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.MarkerPositionSearchRequest;
import io.swagger.model.geno.MarkerPosition;

@Service
public class MarkerPositionService {

	private final MarkerPositionRepository markerPositionRepository;
	private final GenomeMapService genomeMapService;
	private final VariantService variantService;

	public MarkerPositionService(MarkerPositionRepository markerPositionRepository, GenomeMapService genomeMapService,
			VariantService variantService) {
		this.markerPositionRepository = markerPositionRepository;
		this.genomeMapService = genomeMapService;
		this.variantService = variantService;
	}

	public List<MarkerPosition> findMarkerPositions(String mapDbId, String linkageGroupName, String variantDbId,
			Integer maxPosition, Integer minPosition, Metadata metadata) {
		MarkerPositionSearchRequest request = new MarkerPositionSearchRequest();
		if (mapDbId != null)
			request.addMapDbIdsItem(mapDbId);
		if (linkageGroupName != null)
			request.addLinkageGroupNamesItem(linkageGroupName);
		if (variantDbId != null)
			request.addVariantDbIdsItem(variantDbId);
		request.setMinPosition(minPosition);
		request.setMaxPosition(maxPosition);

		return findMarkerPositions(request, metadata);
	}

	public List<MarkerPosition> findMarkerPositions(MarkerPositionSearchRequest request, Metadata metadata) {
		Pageable pageReq = PagingUtility.getPageRequest(metadata);
		SearchQueryBuilder<MarkerPositionEntity> searchQuery = new SearchQueryBuilder<MarkerPositionEntity>(
				MarkerPositionEntity.class).appendList(request.getLinkageGroupNames(), "linkageGroup.linkageGroupName")
						.appendList(request.getVariantDbIds(), "variant.id")
						.appendList(request.getMapDbIds(), "linkageGroup.genomeMap.id")
						.appendNumberRange(request.getMinPosition(), request.getMaxPosition(), "position");

		Page<MarkerPositionEntity> page = markerPositionRepository.findAllBySearch(searchQuery, pageReq);
		List<MarkerPosition> markerPositions = page.map(this::convertFromEntity).getContent();
		PagingUtility.calculateMetaData(metadata, page);
		return markerPositions;
	}

	public MarkerPosition saveMarkerPosition(MarkerPositionWriteRequest request) throws BrAPIServerException {
		if (request.getLinkageGroupDbId() == null || request.getLinkageGroupDbId().isBlank()) {
			throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "linkageGroupDbId is required");
		}
		if (request.getVariantDbId() == null || request.getVariantDbId().isBlank()) {
			throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "variantDbId is required");
		}
		if (request.getPosition() == null) {
			throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "position is required");
		}
		if (request.getMarkerPositionDbId() != null
				&& markerPositionRepository.findById(request.getMarkerPositionDbId()).isPresent()) {
			throw new BrAPIServerException(HttpStatus.CONFLICT,
					"MarkerPosition already exists: " + request.getMarkerPositionDbId());
		}
		MarkerPositionEntity entity = new MarkerPositionEntity();
		if (request.getMarkerPositionDbId() != null && !request.getMarkerPositionDbId().isBlank()) {
			entity.setId(request.getMarkerPositionDbId().trim());
		}
		updateMarkerPositionEntity(entity, request);
		return convertFromEntity(markerPositionRepository.save(entity));
	}

	public MarkerPosition updateMarkerPosition(String markerPositionDbId, MarkerPositionWriteRequest request)
			throws BrAPIServerException {
		MarkerPositionEntity entity = getMarkerPositionEntity(markerPositionDbId);
		updateMarkerPositionEntity(entity, request);
		return convertFromEntity(markerPositionRepository.save(entity));
	}

	public MarkerPosition deleteMarkerPosition(String markerPositionDbId) throws BrAPIServerException {
		MarkerPositionEntity entity = getMarkerPositionEntity(markerPositionDbId);
		MarkerPosition deleted = convertFromEntity(entity);
		try {
			markerPositionRepository.delete(entity);
			markerPositionRepository.flush();
		} catch (DataIntegrityViolationException e) {
			throw new BrAPIServerException(HttpStatus.CONFLICT, "MarkerPosition cannot be deleted");
		}
		return deleted;
	}

	public MarkerPositionEntity getMarkerPositionEntity(String markerPositionDbId) throws BrAPIServerException {
		return markerPositionRepository.findById(markerPositionDbId).orElseThrow(
				() -> new BrAPIServerDbIdNotFoundException("markerPosition", markerPositionDbId, HttpStatus.NOT_FOUND));
	}

	private void updateMarkerPositionEntity(MarkerPositionEntity entity, MarkerPositionWriteRequest request)
			throws BrAPIServerException {
		LinkageGroupEntity linkageGroup = genomeMapService.getLinkageGroupEntity(request.getLinkageGroupDbId());
		VariantEntity variant = variantService.getVariantEntity(request.getVariantDbId());
		assertMarkerVariantUnique(linkageGroup.getId(), variant.getId(), entity.getId());
		if (request.getPosition() < 0) {
			throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "position cannot be negative");
		}
		if (linkageGroup.getMaxMarkerPosition() != null
				&& request.getPosition() > linkageGroup.getMaxMarkerPosition()) {
			throw new BrAPIServerException(HttpStatus.BAD_REQUEST,
					"position cannot exceed linkage group maxPosition");
		}
		entity.setLinkageGroup(linkageGroup);
		entity.setVariant(variant);
		entity.setPosition(request.getPosition());
	}

	private void assertMarkerVariantUnique(String linkageGroupDbId, String variantDbId, String excludeId)
			throws BrAPIServerException {
		SearchQueryBuilder<MarkerPositionEntity> query = new SearchQueryBuilder<MarkerPositionEntity>(
				MarkerPositionEntity.class).appendSingle(linkageGroupDbId, "linkageGroup.id")
						.appendSingle(variantDbId, "variant.id");
		Page<MarkerPositionEntity> page = markerPositionRepository.findAllBySearch(query, PageRequest.of(0, 2));
		for (MarkerPositionEntity existing : page.getContent()) {
			if (excludeId == null || !excludeId.equals(existing.getId())) {
				throw new BrAPIServerException(HttpStatus.CONFLICT,
						"variant already has a marker position on this linkage group");
			}
		}
	}

	private MarkerPosition convertFromEntity(MarkerPositionEntity entity) {
		MarkerPosition position = new MarkerPosition();
		position.setAdditionalInfo(entity.getAdditionalInfoMap());
		if (entity.getLinkageGroup() != null) {
			position.setLinkageGroupName(entity.getLinkageGroup().getLinkageGroupName());
			position.putAdditionalInfoItem("linkageGroupDbId", entity.getLinkageGroup().getId());
			if (entity.getLinkageGroup().getGenomeMap() != null) {
				position.setMapDbId(entity.getLinkageGroup().getGenomeMap().getId());
				position.setMapName(entity.getLinkageGroup().getGenomeMap().getMapName());
			}
		}
		position.setPosition(entity.getPosition());
		if (entity.getVariant() != null) {
			position.setVariantDbId(entity.getVariant().getId());
			position.setVariantName(entity.getVariant().getVariantName());
		}
		position.putAdditionalInfoItem("markerPositionDbId", entity.getId());
		return position;
	}

}
