package de.champonthis.buntspecht.controller;

import java.time.Instant;
import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.querydsl.core.QueryResults;
import com.querydsl.core.Tuple;

import de.champonthis.buntspecht.businesslogic.TurnoverManager;
import de.champonthis.buntspecht.businesslogic.UserManager;
import de.champonthis.buntspecht.controller.model.TurnoverFilterModel;
import de.champonthis.buntspecht.controller.model.TurnoverFilterModel.MinMax;
import de.champonthis.buntspecht.controller.support.EntityResponseStatusException;
import de.champonthis.buntspecht.controller.support.RequestBodyErrors;
import de.champonthis.buntspecht.controller.validation.TurnoverValidator;
import de.champonthis.buntspecht.model.Turnover;
import jakarta.transaction.Transactional;

@RestController
@RequestMapping("/turnovers")
public class TurnoverController extends BaseController {

	@Autowired
	private TurnoverManager turnoverManager;
	@Autowired
	private UserManager userManager;
	@Autowired
	private TurnoverValidator turnoverValidator;

	@PreAuthorize("isAuthenticated()")
	@GetMapping
	@Transactional
	public QueryResults<Turnover> fetch(
			@RequestParam("limit") Optional<Long> limitParameter,
			@RequestParam("offset") Optional<Long> offsetParameter,
			@RequestParam("sort") Optional<String> sort,
			@RequestParam("descending") Optional<Boolean> descending,
			@RequestParam("from") Optional<Instant> from,
			@RequestParam("to") Optional<Instant> to,
			@RequestParam("customer") Optional<String> customer,
			@RequestParam("motif") Optional<String> motif) {

		TurnoverFilterModel filter = new TurnoverFilterModel();
		filter.setCreated(new MinMax<Instant>(from.orElse(null), to.orElse(null)));
		filter.setCustomer(customer.orElse(null));
		filter.setMotif(motif.orElse(null));

		return turnoverManager.fetch(getCurrentUsername(), limitParameter.orElse(15L), offsetParameter.orElse(0L),
				sort.orElse("created"), descending.orElse(false), filter);
	}

	@PreAuthorize("isAuthenticated()")
	@GetMapping("/overview")
	@Transactional
	public Tuple overview(
			@RequestParam("limit") Optional<Long> limitParameter,
			@RequestParam("offset") Optional<Long> offsetParameter,
			@RequestParam("sort") Optional<String> sort,
			@RequestParam("descending") Optional<Boolean> descending,
			@RequestParam("from") Optional<Instant> from,
			@RequestParam("to") Optional<Instant> to,
			@RequestParam("customer") Optional<String> customer,
			@RequestParam("motif") Optional<String> motif) {

		TurnoverFilterModel filter = new TurnoverFilterModel();
		filter.setCreated(new MinMax<Instant>(from.orElse(null), to.orElse(null)));
		filter.setCustomer(customer.orElse(null));
		filter.setMotif(motif.orElse(null));

		List<Tuple> result = turnoverManager.overview(getCurrentUsername(), limitParameter.orElse(15L),
				offsetParameter.orElse(0L), sort.orElse("username"),
				descending.orElse(false), filter).getResults();

		if (result.isEmpty()) {
			return null;
		}

		return result.get(0);
	}

	@PreAuthorize("isAuthenticated()")
	@GetMapping("/{id}")
	@Transactional
	public Turnover get(@PathVariable("id") Long id) {
		Turnover turnover = turnoverManager.get(id);

		if (turnover == null || !getCurrentUsername().equals(turnover.getUsername())) {
			throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
		}

		return turnover;
	}

	@PreAuthorize("isAuthenticated()")
	@PostMapping
	@Transactional
	public Turnover create(@RequestBody Turnover turnover) {
		Errors errors = new RequestBodyErrors(turnover);
		turnoverValidator.validate(turnover, errors);

		if (errors.hasErrors()) {
			throw new EntityResponseStatusException(errors.getAllErrors(), HttpStatus.CONFLICT);
		}

		if (!hasRole("ROLE_ADMIN") || !StringUtils.hasText(turnover.getUsername())
				|| !userManager.exists(turnover.getUsername())) {
			turnover.setUsername(getCurrentUsername());
		}
		turnover.setCreated(Instant.now());
		turnover.setUpdated(turnover.getCreated());

		return turnoverManager.save(turnover);
	}

	@PreAuthorize("isAuthenticated()")
	@PatchMapping
	@Transactional
	public Turnover update(@RequestBody Turnover turnover) {
		Errors errors = new RequestBodyErrors(turnover);
		turnoverValidator.validate(turnover, errors);

		if (errors.hasErrors() || turnover.getId() == null || turnover.getId() == 0L
				|| !turnoverManager.exists(turnover.getId())) {
			throw new EntityResponseStatusException(errors.getAllErrors(), HttpStatus.CONFLICT);
		}

		if (!hasRole("ROLE_ADMIN")) {
			Turnover existing = turnoverManager.get(turnover.getId());
			if (!getCurrentUsername().equals(existing.getUsername())) {
				throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
			}
			turnover.setUsername(getCurrentUsername());
		}

		Turnover existing = turnoverManager.get(turnover.getId());

		if (existing.equals(turnover)) {
			throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED);
		}

		turnover.setUpdated(Instant.now());

		return turnoverManager.save(turnover);
	}

	@PreAuthorize("hasRole('ROLE_ADMIN')")
	@DeleteMapping("/{id}")
	public void deleteById(@PathVariable("id") Long id) {
		if (!turnoverManager.exists(id)) {
			throw new EntityResponseStatusException(HttpStatus.NO_CONTENT);
		}

		turnoverManager.deleteById(id);
	}
}
