package com.oguerreiro.resilient.web.rest;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Sort;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.oguerreiro.resilient.repository.InputDataRepository;
import com.oguerreiro.resilient.service.InputDataService;
import com.oguerreiro.resilient.service.criteria.InputDataCriteria;
import com.oguerreiro.resilient.service.dto.InputDataDTO;
import com.oguerreiro.resilient.service.query.InputDataQueryService;
import com.oguerreiro.resilient.web.rest.errors.BadRequestAlertException;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import tech.jhipster.web.util.HeaderUtil;
import tech.jhipster.web.util.ResponseUtil;

/**
 * REST controller for managing {@link com.oguerreiro.resilient.domain.InputData}.
 */
@RestController
@RequestMapping("/api/input-data")
public class InputDataResource {

  private final Logger log = LoggerFactory.getLogger(InputDataResource.class);

  private static final String ENTITY_NAME = "inputData";

  @Value("${jhipster.clientApp.name}")
  private String applicationName;

  private final InputDataService inputDataService;

  private final InputDataRepository inputDataRepository;

  private final InputDataQueryService inputDataQueryService;

  public InputDataResource(InputDataService inputDataService, InputDataRepository inputDataRepository,
      InputDataQueryService inputDataQueryService) {
    this.inputDataService = inputDataService;
    this.inputDataRepository = inputDataRepository;
    this.inputDataQueryService = inputDataQueryService;
  }

  /**
   * {@code POST  /input-data} : Create a new inputData.
   *
   * @param  inputDataDTO       the inputDataDTO to create.
   * @return                    the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new
   *                            inputDataDTO, or with status {@code 400 (Bad Request)} if the inputData has already an
   *                            ID.
   * @throws URISyntaxException if the Location URI syntax is incorrect.
   */
  @PostMapping("")
  public ResponseEntity<InputDataDTO> createInputData(@Valid @RequestBody InputDataDTO inputDataDTO)
      throws URISyntaxException {
    log.debug("REST request to save InputData : {}", inputDataDTO);
    if (inputDataDTO.getId() != null) {
      throw new BadRequestAlertException("A new inputData cannot already have an ID", ENTITY_NAME, "idexists");
    }
    inputDataDTO = inputDataService.save(inputDataDTO);
    return ResponseEntity.created(new URI("/api/input-data/" + inputDataDTO.getId())).headers(
        HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, inputDataDTO.getId().toString())).body(
            inputDataDTO);
  }

  /**
   * {@code PUT  /input-data/:id} : Updates an existing inputData.
   *
   * @param  id                 the id of the inputDataDTO to save.
   * @param  inputDataDTO       the inputDataDTO to update.
   * @return                    the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated
   *                            inputDataDTO,
   *                            or with status {@code 400 (Bad Request)} if the inputDataDTO is not valid,
   *                            or with status {@code 500 (Internal Server Error)} if the inputDataDTO couldn't be
   *                            updated.
   * @throws URISyntaxException if the Location URI syntax is incorrect.
   */
  @PutMapping("/{id}")
  public ResponseEntity<InputDataDTO> updateInputData(@PathVariable(value = "id", required = false) final Long id,
      @Valid @RequestBody InputDataDTO inputDataDTO) throws URISyntaxException {
    log.debug("REST request to update InputData : {}, {}", id, inputDataDTO);
    if (inputDataDTO.getId() == null) {
      throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
    }
    if (!Objects.equals(id, inputDataDTO.getId())) {
      throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid");
    }

    if (!inputDataRepository.existsById(id)) {
      throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound");
    }

    inputDataDTO = inputDataService.update(inputDataDTO);
    return ResponseEntity.ok().headers(
        HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, inputDataDTO.getId().toString())).body(
            inputDataDTO);
  }

  /**
   * {@code PATCH  /input-data/:id} : Partial updates given fields of an existing inputData, field will ignore if it is
   * null
   *
   * @param  id                 the id of the inputDataDTO to save.
   * @param  inputDataDTO       the inputDataDTO to update.
   * @return                    the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated
   *                            inputDataDTO,
   *                            or with status {@code 400 (Bad Request)} if the inputDataDTO is not valid,
   *                            or with status {@code 404 (Not Found)} if the inputDataDTO is not found,
   *                            or with status {@code 500 (Internal Server Error)} if the inputDataDTO couldn't be
   *                            updated.
   * @throws URISyntaxException if the Location URI syntax is incorrect.
   */
  @PatchMapping(value = "/{id}", consumes = { "application/json", "application/merge-patch+json" })
  public ResponseEntity<InputDataDTO> partialUpdateInputData(
      @PathVariable(value = "id", required = false) final Long id, @NotNull @RequestBody InputDataDTO inputDataDTO)
      throws URISyntaxException {
    log.debug("REST request to partial update InputData partially : {}, {}", id, inputDataDTO);
    if (inputDataDTO.getId() == null) {
      throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
    }
    if (!Objects.equals(id, inputDataDTO.getId())) {
      throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid");
    }

    if (!inputDataRepository.existsById(id)) {
      throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound");
    }

    Optional<InputDataDTO> result = inputDataService.partialUpdate(inputDataDTO);

    return ResponseUtil.wrapOrNotFound(result,
        HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, inputDataDTO.getId().toString()));
  }

  /**
   * {@code GET  /input-data} : get all the inputData.
   *
   * @param  criteria the criteria which the requested entities should match.
   * @return          the {@link ResponseEntity} with status {@code 200 (OK)} and the list of inputData in body.
   */
  @GetMapping("")
  public ResponseEntity<List<InputDataDTO>> getAllInputData(InputDataCriteria criteria) {
    log.debug("REST request to get InputData by criteria: {}", criteria);

    Sort sort = Sort.by(Sort.Order.asc("variable.code"));
    List<InputDataDTO> entityList = inputDataQueryService.findByCriteria(criteria, sort);
    return ResponseEntity.ok().body(entityList);
  }

  /**
   * {@code GET  /input-data/count} : count all the inputData.
   *
   * @param  criteria the criteria which the requested entities should match.
   * @return          the {@link ResponseEntity} with status {@code 200 (OK)} and the count in body.
   */
  @GetMapping("/count")
  public ResponseEntity<Long> countInputData(InputDataCriteria criteria) {
    log.debug("REST request to count InputData by criteria: {}", criteria);
    return ResponseEntity.ok().body(inputDataQueryService.countByCriteria(criteria));
  }

  /**
   * {@code GET  /input-data/:id} : get the "id" inputData.
   *
   * @param  id the id of the inputDataDTO to retrieve.
   * @return    the {@link ResponseEntity} with status {@code 200 (OK)} and with body the inputDataDTO, or with status
   *            {@code 404 (Not Found)}.
   */
  @GetMapping("/{id}")
  public ResponseEntity<InputDataDTO> getInputData(@PathVariable("id") Long id) {
    log.debug("REST request to get InputData : {}", id);
    Optional<InputDataDTO> inputDataDTO = inputDataService.findOne(id);
    return ResponseUtil.wrapOrNotFound(inputDataDTO);
  }

  /**
   * {@code DELETE  /input-data/:id} : delete the "id" inputData.
   *
   * @param  id the id of the inputDataDTO to delete.
   * @return    the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}.
   */
  @DeleteMapping("/{id}")
  public ResponseEntity<Void> deleteInputData(@PathVariable("id") Long id) {
    log.debug("REST request to delete InputData : {}", id);
    inputDataService.delete(id);
    return ResponseEntity.noContent().headers(
        HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, id.toString())).build();
  }

  @GetMapping("/export/{periodId}")
  public ResponseEntity<byte[]> export(@PathVariable("periodId") Long periodId) {
    log.debug("REST request to export : {}", periodId);

    byte[] fileContent = this.inputDataService.export(periodId);

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    headers.setContentDisposition(ContentDisposition.attachment().filename("report.xlsx").build());

    return new ResponseEntity<>(fileContent, headers, HttpStatus.OK);
  }

}
