package com.oguerreiro.resilient.service;

import java.time.Instant;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.oguerreiro.resilient.activities.inputDataImporter.POIFileImporterActivity;
import com.oguerreiro.resilient.domain.InputDataUpload;
import com.oguerreiro.resilient.domain.Organization;
import com.oguerreiro.resilient.domain.enumeration.OrganizationNature;
import com.oguerreiro.resilient.domain.enumeration.UploadStatus;
import com.oguerreiro.resilient.repository.InputDataUploadRepository;
import com.oguerreiro.resilient.security.SecurityUtils;
import com.oguerreiro.resilient.service.dto.InputDataUploadDTO;
import com.oguerreiro.resilient.service.interceptors.BeforeDeleteServiceInterceptor;
import com.oguerreiro.resilient.service.interceptors.BeforeSaveServiceInterceptor;
import com.oguerreiro.resilient.service.interceptors.BeforeUpdateServiceInterceptor;
import com.oguerreiro.resilient.service.mapper.InputDataUploadMapper;

/**
 * Service Implementation for managing {@link com.oguerreiro.resilient.domain.InputDataUpload}.
 */
@Service
@Transactional
public class InputDataUploadService extends AbstractResilientService<InputDataUploadDTO, InputDataUpload, Long>
    implements BeforeSaveServiceInterceptor<InputDataUploadDTO, InputDataUpload, Long>,
    BeforeDeleteServiceInterceptor<InputDataUploadDTO, InputDataUpload, Long>,
    BeforeUpdateServiceInterceptor<InputDataUploadDTO, InputDataUpload, Long> {

  private final InputDataUploadRepository inputDataUploadRepository;
  private final POIFileImporterActivity poiFileImporterActivity;
  private final InputDataService inputDataService;
  private final OrganizationService organizationService;
  private final InputDataUploadLogService inputDataUploadLogService;
  private final InputDataUploadMapper inputDataUploadMapper;

  public InputDataUploadService(InputDataUploadRepository inputDataUploadRepository,
      InputDataUploadMapper inputDataUploadMapper, POIFileImporterActivity poiFileImporterActivity,
      InputDataService inputDataService, InputDataUploadLogService inputDataUploadLogService,
      OrganizationService organizationService) {
    super(InputDataUpload.class, inputDataUploadRepository, inputDataUploadMapper);
    this.inputDataUploadRepository = inputDataUploadRepository;
    this.poiFileImporterActivity = poiFileImporterActivity;
    this.inputDataService = inputDataService;
    this.inputDataUploadLogService = inputDataUploadLogService;
    this.inputDataUploadMapper = inputDataUploadMapper;
    this.organizationService = organizationService;
  }

  @Transactional()
  public void doLoad(Long id) {
    log.debug("Request to doload InputDataUpload : {}", id);

    InputDataUpload inputDataUpload = inputDataUploadRepository.getReferenceById(id);

    // Validate state. Only if UPLOADED
    if (!inputDataUpload.getState().equals(UploadStatus.UPLOADED)) {
      //throw exception. 
    }

    // Import the variable data
    this.poiFileImporterActivity.doActivity(inputDataUpload.getActivityDomainClass(), inputDataUpload.getId());
  }

  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void updateStateWithNewTransaction(InputDataUpload inputDataUpload, UploadStatus newUploadStatus) {
    log.debug("Request to delete updateStateinNewTransaction : {}", inputDataUpload, newUploadStatus);

    inputDataUpload = inputDataUploadRepository.getReferenceById(inputDataUpload.getId());
    inputDataUpload.setState(newUploadStatus);

    inputDataUploadRepository.save(inputDataUpload);
  }

  @Transactional(readOnly = true)
  public List<InputDataUploadDTO> findAllOwned(List<Organization> organizations) {
    log.debug("Request to findAllOwned " + this.getClass().getSimpleName() + "'s.");
    return inputDataUploadRepository.findByOwnerIn(organizations).stream().map(
        this.inputDataUploadMapper::toDto).collect(Collectors.toCollection(LinkedList::new));
  }

  private void validateUserAccessToOwner(InputDataUpload inputDataUpload) {
    Organization userParent = SecurityUtils.getCurrentUserParentOrganization();
    if (userParent.equals(inputDataUpload.getOwner())) {
      return; //User has access, just exit
    }

    // Try the children
    userParent = super.getEntityManager().merge(userParent);  //Attach
    Boolean haveAccess = this.organizationService.getAllChildrenOf(userParent,
        OrganizationNature.ORGANIZATION).stream().anyMatch((org) -> (org.equals(inputDataUpload.getOwner())));

    if (!haveAccess) {
      throw new RuntimeException(
          "User doesn't have access to the Organization : " + inputDataUpload.getOwner().getCode());
    }
  }

  /* ServiceInterceptor's implementation */
  @Override
  public void beforeDelete(Long id) {
    // Load
    InputDataUpload inputDataUpload = inputDataUploadRepository.getReferenceById(id);

    this.validateUserAccessToOwner(inputDataUpload);

    // Delete any previous InputData inserted by this InputDataUpload file.
    this.inputDataService.deleteAllInputDataWithSource(inputDataUpload);

    // Clear any associated InputDataUploadLogs.
    this.inputDataUploadLogService.deleteAllByInputDataUpload(inputDataUpload);
  }

  @Override
  public InputDataUpload beforeSave(InputDataUpload domain) {
    InputDataUpload inputDataUpload = (InputDataUpload) domain;

    this.validateUserAccessToOwner(domain);

    //Initial setup (defaults)
    inputDataUpload.setCreationDate(Instant.now());
    inputDataUpload.setCreationUsername(
        SecurityUtils.getCurrentUserLogin().orElseThrow(() -> new RuntimeException("Value not found")));
    inputDataUpload.setState(UploadStatus.UPLOADED);

    return inputDataUpload;
  }

  @Override
  public InputDataUpload beforeUpdate(InputDataUpload domain) {
    this.validateUserAccessToOwner(domain);
    return domain;
  }
}
