package it.micegroup.gestioneferie.pianificazioneferie.controller;

import lombok.RequiredArgsConstructor;
import it.micegroup.gestioneferie.pianificazioneferie.entity.DpianiFerie;
import it.micegroup.gestioneferie.pianificazioneferie.assembler.DpianiFerieModelAssembler;
import it.micegroup.gestioneferie.pianificazioneferie.criteria.DpianiFerieCriteria;
import it.micegroup.gestioneferie.pianificazioneferie.service.DpianiFerieService;
import it.micegroup.gestioneferie.pianificazioneferie.service.DrichiesteApprovazPfService;
import it.micegroup.gestioneferie.pianificazioneferie.service.DrichAdesionePfService;
import it.micegroup.gestioneferie.pianificazioneferie.service.DammtiPianiFerieService;
import it.micegroup.gestioneferie.pianificazioneferie.entity.DrichiesteApprovazPf;
import it.micegroup.gestioneferie.pianificazioneferie.entity.DrichAdesionePf;
import it.micegroup.gestioneferie.pianificazioneferie.entity.DammtiPianiFerie;
import it.micegroup.gestioneferie.pianificazioneferie.entity.DstatiPianoFerie;
import it.micegroup.gestioneferie.pianificazioneferie.entity.DstatiPianoFerieKey;

import it.micegroup.voila2runtime.exception.*;
import it.micegroup.gestioneferie.libs.controller.BaseController;
import it.micegroup.gestioneferie.pianificazioneferie.dto.DpianiFerieDto;
import it.micegroup.gestioneferie.pianificazioneferie.dto.DpianiFerieWriteDto;
import it.micegroup.gestioneferie.libs.response.BaseResponse;
import it.micegroup.gestioneferie.libs.request.BaseRequest;
import javax.annotation.PostConstruct;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.PagedModel;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import javax.validation.Valid;
import java.net.URI;
import java.util.Locale;
import java.util.Optional;

import java.util.Collection;
import org.modelmapper.PropertyMap;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/dpiani-ferie", produces = MediaType.APPLICATION_JSON_VALUE)
public class DpianiFerieController extends BaseController<BaseResponse<DpianiFerieDto>> {

	/// ENTITY SERVICE
	private final DpianiFerieService dpianiFerieService;
	private final DpianiFerieModelAssembler dpianiFerieModelAssembler;
	private final PagedResourcesAssembler<DpianiFerie> pagedResourcesAssembler;
	private final ModelMapper modelMapper;
	// CHILD SERVICES
	private final DrichiesteApprovazPfService drichiesteApprovazPfService;
	private final DrichAdesionePfService drichAdesionePfService;
	private final DammtiPianiFerieService dammtiPianiFerieService;

	// CONSTRUCTOR
	@PostConstruct
	public void postConstruct() {
		modelMapper.addMappings(new PropertyMap<DpianiFerieWriteDto, DpianiFerie>() {
			@Override
			protected void configure() {
				skip(destination.getTheDrichiesteApprovazPf());
				skip(destination.getTheDrichAdesionePf());
				skip(destination.getTheDammtiPianiFerie());
			}
		});
	}

	@SuppressWarnings("unused")
	private static final Logger LOGGER = LogManager.getLogger(DpianiFerieController.class);

	// API
	@GetMapping
	@PreAuthorize("hasRole(@permissionHolder.DPIANI_FERIE_FIND_PAGINATED.toString())")
	public ResponseEntity<BaseResponse<PagedModel<DpianiFerieDto>>> findPaginated(DpianiFerieCriteria criteria,
			Pageable pageable) {
		Page<DpianiFerie> page = dpianiFerieService.findByCriteria(criteria, pageable);
		return toResponseEntityPaged(page, null);
	}

	/**
	 * {@code GET  /dpiani-ferie/:objectKey} : get the dpiani-ferie with given
	 * objectKey.
	 *
	 * @param objectKey the objectKey of the dpiani-ferie to retrieve.
	 * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body
	 *         the dpiani-ferie, or with status {@code 404 (Not Found)}.
	 */
	@GetMapping("/{objectKey:.+}")
	@PreAuthorize("hasRole(@permissionHolder.DPIANI_FERIE_FIND_BY_ID.toString())")
	public ResponseEntity<BaseResponse<DpianiFerieDto>> getByObjectKey(@PathVariable String objectKey) {
		return toResponseEntity(dpianiFerieService.findByObjectKey(objectKey), null, HttpStatus.OK);
	}

	/**
	 * {@code PUT  /dpiani-ferie} : Updates an existing DpianiFerie.
	 *
	 * @param DpianiFerie the DpianiFerie to update.
	 * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body
	 *         the updated DpianiFerie, or with status {@code 400 (Bad Request)} if
	 *         the DpianiFerie is not valid, or with status
	 *         {@code 500 (Internal Server Error)} if the DpianiFerie couldn't be
	 *         updated.
	 */
	@PutMapping
	@PreAuthorize("hasRole(@permissionHolder.DPIANI_FERIE_UPDATE.toString())")
	public ResponseEntity<?> update(@Valid @RequestBody BaseRequest<DpianiFerieWriteDto> request) {
		return toResponseEntity(
				Optional.of(dpianiFerieService.bulkUpdate(modelMapper.map(request.getData(), DpianiFerie.class))), null,
				HttpStatus.OK);
	}

	/**
	 * {@code POST  /dpiani-ferie} : Create a new DpianiFerie.
	 *
	 * @param DpianiFerie the DpianiFerie to create.
	 * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with
	 *         body the new DpianiFerie, or with status {@code 400 (Bad Request)} if
	 *         the DpianiFerie has already an ID.
	 * @throws URISyntaxException if the Location URI syntax is incorrect.
	 */
	@PostMapping
	@PreAuthorize("hasRole(@permissionHolder.DPIANI_FERIE_CREATE.toString())")
	public ResponseEntity<?> save(@Valid @RequestBody BaseRequest<DpianiFerieWriteDto> request) {
		ResponseEntity<?> response = null;
		DpianiFerie dpianiFerieToInsert = childMap(request.getData());
		try {
			DpianiFerie newDpianiFerie = dpianiFerieService
					.insert(modelMapper.map(dpianiFerieToInsert, DpianiFerie.class));
			URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
					.buildAndExpand(newDpianiFerie.getSequIdPianoFerie()).toUri();
			HttpHeaders headers = new HttpHeaders();
			headers.setLocation(location);
			response = toResponseEntity(Optional.of(newDpianiFerie), headers, HttpStatus.CREATED);
		} catch (Exception exception) {
			LOGGER.error("Exception:", exception);
		}
		return response;
	}

	private DpianiFerie childMap(DpianiFerieWriteDto data) {
		DpianiFerie dpianiFerieToInsert = modelMapper.map(data, DpianiFerie.class);
		Collection<DrichiesteApprovazPf> theDrichiesteApprovazPfs = data.getTheDrichiesteApprovazPf().stream()
				.map(element -> modelMapper.map(element, DrichiesteApprovazPf.class))
				.peek(element -> element.setTheDpianiFerie(dpianiFerieToInsert)).collect(Collectors.toList());
		dpianiFerieToInsert.setTheDrichiesteApprovazPf(theDrichiesteApprovazPfs);
		Collection<DrichAdesionePf> theDrichAdesionePfs = data.getTheDrichAdesionePf().stream()
				.map(element -> modelMapper.map(element, DrichAdesionePf.class))
				.peek(element -> element.setTheDpianiFerie(dpianiFerieToInsert)).collect(Collectors.toList());
		dpianiFerieToInsert.setTheDrichAdesionePf(theDrichAdesionePfs);
		Collection<DammtiPianiFerie> theDammtiPianiFeries = data.getTheDammtiPianiFerie().stream()
				.map(element -> modelMapper.map(element, DammtiPianiFerie.class))
				.peek(element -> element.setTheDpianiFerie(dpianiFerieToInsert)).collect(Collectors.toList());
		dpianiFerieToInsert.setTheDammtiPianiFerie(theDammtiPianiFeries);
		return dpianiFerieToInsert;
	}

	/**
	 * {@code DELETE  /dpiani-ferie/:objectKey} : delete the dpiani-ferie with given
	 * objectKey.
	 *
	 * @param objectKey the objectKey of the DpianiFerie to delete.
	 * @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}.
	 */
	@DeleteMapping("/{objectKey:.+}")
	@PreAuthorize("hasRole(@permissionHolder.DPIANI_FERIE_DELETE.toString())")
	public ResponseEntity<?> delete(@PathVariable String objectKey) {
		dpianiFerieService.deleteById(dpianiFerieService.findByObjectKey(objectKey).get().getSequIdPianoFerie());
		return new ResponseEntity<>(new BaseResponse<>(objectKey), HttpStatus.OK);
	}

	/**
	 * GET /the-dstati-piano-ferie_id: Search all DpianiFerie for the given
	 * DstatiPianoFerie (parent)
	 * 
	 * @param OK       of DstatiPianoFerie
	 * @param pageable
	 * @return Page of DpianiFerie for the given DstatiPianoFerie (parent)
	 */
	@GetMapping("/findByTheDstatiPianoFerie/{dstatiPianoFerieObjectKey:.+}")
	@PreAuthorize("hasRole(@permissionHolder.DPIANI_FERIE_FIND_BY_DSTATI_PIANO_FERIE.toString())")
	public ResponseEntity<BaseResponse<PagedModel<DpianiFerieDto>>> findByDstatiPianoFerie(
			@PathVariable String dstatiPianoFerieObjectKey, Pageable pageable) {
		DstatiPianoFerie key = new DstatiPianoFerie(dstatiPianoFerieObjectKey);
		return toResponseEntityPaged(dpianiFerieService.findByTheDstatiPianoFerie(key, pageable), null);
	}

	/**
	 * GET /id/drichieste-approvaz-pf: Search all DrichiesteApprovazPf (childs) for
	 * the given DpianiFerie
	 * 
	 * @param objectKey ObjectKey of DpianiFerie
	 * @param pageable
	 * @return Page of DrichiesteApprovazPf for the given DpianiFerie
	 */
	@GetMapping("/{objectKey:.+}/drichieste-approvaz-pf")
	@PreAuthorize("hasRole(@permissionHolder.DPIANI_FERIE_FIND_BY_THE_DRICHIESTE_APPROVAZ_PF_OBJECT_KEY.toString())")
	public ResponseEntity<Page<DrichiesteApprovazPf>> getTheDrichiesteApprovazPfByObjectKey(
			@PathVariable String objectKey, Pageable pageable) {
		DpianiFerie dpianiFerie = new DpianiFerie();
		dpianiFerie.setObjectKey(objectKey);
		return ResponseEntity.ok().body(drichiesteApprovazPfService.findByTheDpianiFerie(dpianiFerie, pageable));
	}

	/**
	 * GET /id/drich-adesione-pf: Search all DrichAdesionePf (childs) for the given
	 * DpianiFerie
	 * 
	 * @param objectKey ObjectKey of DpianiFerie
	 * @param pageable
	 * @return Page of DrichAdesionePf for the given DpianiFerie
	 */
	@GetMapping("/{objectKey:.+}/drich-adesione-pf")
	@PreAuthorize("hasRole(@permissionHolder.DPIANI_FERIE_FIND_BY_THE_DRICH_ADESIONE_PF_OBJECT_KEY.toString())")
	public ResponseEntity<Page<DrichAdesionePf>> getTheDrichAdesionePfByObjectKey(@PathVariable String objectKey,
			Pageable pageable) {
		DpianiFerie dpianiFerie = new DpianiFerie();
		dpianiFerie.setObjectKey(objectKey);
		return ResponseEntity.ok().body(drichAdesionePfService.findByTheDpianiFerie(dpianiFerie, pageable));
	}

	/**
	 * GET /id/dammti-piani-ferie: Search all DammtiPianiFerie (childs) for the
	 * given DpianiFerie
	 * 
	 * @param objectKey ObjectKey of DpianiFerie
	 * @param pageable
	 * @return Page of DammtiPianiFerie for the given DpianiFerie
	 */
	@GetMapping("/{objectKey:.+}/dammti-piani-ferie")
	@PreAuthorize("hasRole(@permissionHolder.DPIANI_FERIE_FIND_BY_THE_DAMMTI_PIANI_FERIE_OBJECT_KEY.toString())")
	public ResponseEntity<Page<DammtiPianiFerie>> getTheDammtiPianiFerieByObjectKey(@PathVariable String objectKey,
			Pageable pageable) {
		DpianiFerie dpianiFerie = new DpianiFerie();
		dpianiFerie.setObjectKey(objectKey);
		return ResponseEntity.ok().body(dammtiPianiFerieService.findByTheDpianiFerie(dpianiFerie, pageable));
	}

	private ResponseEntity<BaseResponse<DpianiFerieDto>> toResponseEntity(Optional<DpianiFerie> maybeResponse,
			HttpHeaders header, HttpStatus status) {
		return maybeResponse
				.map(response -> new ResponseEntity<>(new BaseResponse<>(dpianiFerieModelAssembler.toModel(response)),
						header, status))
				.orElseThrow(() -> new NotFoundException(DpianiFerie.class, new Locale("it")));
	}

	private ResponseEntity<BaseResponse<PagedModel<DpianiFerieDto>>> toResponseEntityPaged(Page<DpianiFerie> list,
			HttpHeaders header) {
		BaseResponse<PagedModel<DpianiFerieDto>> collModel = new BaseResponse<>(
				pagedResourcesAssembler.toModel(list, dpianiFerieModelAssembler));
		return ResponseEntity.ok().headers(header).body(collModel);
	}

	@SuppressWarnings("unused")
	private DpianiFerie dtoToEntity(DpianiFerieDto dto) {
		return new ModelMapper().map(dto, DpianiFerie.class);
	}
}
