/**
 * 
 */
package de.bstly.board.controller;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
import java.util.Scanner;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
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.google.common.collect.Lists;

import de.bstly.board.businesslogic.EntryManager;
import de.bstly.board.businesslogic.SettingsManager;
import de.bstly.board.businesslogic.UserManager;
import de.bstly.board.businesslogic.ViewManager;
import de.bstly.board.businesslogic.VoteManager;
import de.bstly.board.controller.model.EntryFilter;
import de.bstly.board.controller.support.EntityResponseStatusException;
import de.bstly.board.controller.support.RequestBodyErrors;
import de.bstly.board.controller.validation.EntryValidator;
import de.bstly.board.model.Entry;
import de.bstly.board.model.View;
import de.bstly.board.model.Vote;
import de.bstly.board.model.support.EntryStatus;
import de.bstly.board.model.support.EntryType;
import de.bstly.board.model.support.FlaggedStatus;
import de.bstly.board.model.support.Types;
import de.bstly.board.model.support.VoteType;

/**
 * The Class EntryController.
 */
@RestController
@RequestMapping("/entries")
public class EntryController extends BaseController {

	@Autowired
	private EntryManager entryManager;
	@Autowired
	private UserManager userManager;
	@Autowired
	private EntryValidator entryValidator;
	@Autowired
	private VoteManager voteManager;
	@Autowired
	private SettingsManager settingsManager;
	@Autowired
	private ViewManager viewManager;

	/**
	 * Fetch by user page.
	 *
	 * @param name              the name
	 * @param usernameParameter the username parameter
	 * @param pageParameter     the page parameter
	 * @param sizeParameter     the size parameter
	 * @param ascParameter      the asc parameter
	 * @param ignoreParameter   the ignore parameter
	 * @return the page
	 */
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/{name}")
	public Page<Entry> fetchByView(@PathVariable("name") String name,
			@RequestParam("user") Optional<String> usernameParameter,
			@RequestParam("page") Optional<Integer> pageParameter,
			@RequestParam("size") Optional<Integer> sizeParameter,
			@RequestParam("date") Optional<Instant> dateParameter,
			@RequestParam("tags") Optional<List<String>> tagsParameter,
			@RequestParam("excludedTags") Optional<List<String>> excludedTagsParameter,
			@RequestParam("type") Optional<EntryType> typeParameter,
			@RequestParam("gravity") Optional<Float> gravityParameter,
			@RequestParam("asc") Optional<Boolean> ascParameter,
			@RequestParam("ignore") Optional<List<String>> ignoreParameter) {

		View view = viewManager.get(usernameParameter.orElse(getCurrentUsername()),
				name);

		if (view == null || usernameParameter.isPresent() && !view.isPublicView()) {
			throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
		}

		if (sizeParameter.isPresent() && sizeParameter.get() > 100) {
			sizeParameter = Optional.of(100);
		}

		EntryFilter filter = buildFilter(dateParameter.orElse(null), FlaggedStatus.NORMAL,
				tagsParameter.orElse(null), excludedTagsParameter.orElse(null),
				typeParameter.orElse(null));

		filter.setFixedTags(view.getTags());
		filter.setFixedExcludedTags(view.getExcludedTags());

		Page<Entry> entries = null;

		switch (view.getSorting()) {
		case TOP:
			entries = entryManager.fetchByRanking(getCurrentUsername(), filter, getGravity(),
					pageParameter.orElse(0), sizeParameter.orElse(settingsManager.getPageSize()),
					ascParameter.orElse(false));
			break;
		case NEW:
			entries = entryManager.fetchByDate(getCurrentUsername(), filter,
					pageParameter.orElse(0), sizeParameter.orElse(settingsManager.getPageSize()),
					ascParameter.orElse(false));
			break;
		case HOT:
			entries = entryManager.fetchByComments(getCurrentUsername(), filter, getGravity(),
					pageParameter.orElse(0), sizeParameter.orElse(settingsManager.getPageSize()),
					ascParameter.orElse(false));
			break;
		case LAST:
			entries = entryManager.fetchByLastComment(getCurrentUsername(), filter,
					pageParameter.orElse(0), sizeParameter.orElse(settingsManager.getPageSize()),
					ascParameter.orElse(false));
			break;
		}

		if (entries == null) {
			throw new EntityResponseStatusException(HttpStatus.BAD_REQUEST);
		}

		List<String> ignore = ignoreParameter.orElse(Lists.newArrayList());
		entryManager.applyMetadata(getCurrentUsername(), userManager.getKarma(getCurrentUsername()),
				entries.getContent(), ignore);
		return entries;
	}

	/**
	 * Fetch by user.
	 *
	 * @param username              the username
	 * @param pageParameter         the page parameter
	 * @param sizeParameter         the size parameter
	 * @param dateParameter         the date parameter
	 * @param tagsParameter         the tags parameter
	 * @param excludedTagsParameter the excluded tags parameter
	 * @param typeParameter         the type parameter
	 * @param ascParameter          the asc parameter
	 * @param ignoreParameter       the ignore parameter
	 * @return the page
	 */
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/byuser/{username}")
	public Page<Entry> fetchByUser(@PathVariable("username") String username,
			@RequestParam("page") Optional<Integer> pageParameter,
			@RequestParam("size") Optional<Integer> sizeParameter,
			@RequestParam("date") Optional<Instant> dateParameter,
			@RequestParam("tags") Optional<List<String>> tagsParameter,
			@RequestParam("excludedTags") Optional<List<String>> excludedTagsParameter,
			@RequestParam("type") Optional<EntryType> typeParameter,
			@RequestParam("asc") Optional<Boolean> ascParameter,
			@RequestParam("ignore") Optional<List<String>> ignoreParameter) {

		if (sizeParameter.isPresent() && sizeParameter.get() > 100) {
			sizeParameter = Optional.of(100);
		}

		if (dateParameter.isPresent() && dateParameter.get().isAfter(Instant.now())) {
			dateParameter = Optional.of(Instant.now());
		}

		EntryFilter filter = buildFilter(dateParameter.orElse(null), FlaggedStatus.NORMAL,
				tagsParameter.orElse(null), excludedTagsParameter.orElse(null),
				typeParameter.orElse(null));

		Page<Entry> entries = entryManager.fetchByUser(getCurrentUsername(), username, filter,
				pageParameter.orElse(0), sizeParameter.orElse(settingsManager.getPageSize()),
				ascParameter.orElse(false));
		List<String> ignore = ignoreParameter.orElse(Lists.newArrayList());
		entryManager.applyMetadata(getCurrentUsername(), userManager.getKarma(getCurrentUsername()),
				entries.getContent(), ignore);
		return entries;
	}

	/**
	 * Builds the filter.
	 *
	 * @param date          the date
	 * @param flaggedStatus the flagged status
	 * @param tags          the tags
	 * @param excludedTags  the excluded tags
	 * @param type          the type
	 * @return the entry filter
	 */
	protected EntryFilter buildFilter(Instant date, FlaggedStatus flaggedStatus, List<String> tags,
			List<String> excludedTags, EntryType type) {
		EntryFilter filter = new EntryFilter();
		filter.setDate(date);
		filter.setFlaggedStatus(flaggedStatus);
		filter.setEntryType(type);
		filter.setTags(tags);
		filter.setExcludedTags(excludedTags);
		return filter;
	}

	/**
	 * Gets the entry.
	 *
	 * @param id              the id
	 * @param ignoreParameter the ignore parameter
	 * @return the entry
	 */
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/entry/{id}")
	public Entry getEntry(@PathVariable("id") Long id,
			@RequestParam("ignore") Optional<List<String>> ignoreParameter) {
		Entry entry = entryManager.get(id);

		if (entry == null) {
			throw new EntityResponseStatusException(HttpStatus.NOT_FOUND);
		}

		List<String> ignore = ignoreParameter.orElse(Lists.newArrayList());
		entryManager.applyMetadata(getCurrentUsername(), userManager.getKarma(getCurrentUsername()),
				entry, ignore);

		return entry;
	}

	/**
	 * Creates the entry.
	 *
	 * @param entry           the entry
	 * @param ignoreParameter the ignore parameter
	 * @return the entry
	 */
	@PreAuthorize("isAuthenticated()")
	@PostMapping()
	public Entry createEntry(@RequestBody Entry entry,
			@RequestParam("ignore") Optional<List<String>> ignoreParameter) {
		RequestBodyErrors bindingResult = new RequestBodyErrors(entry);
		entryValidator.validate(entry, bindingResult);

		if (bindingResult.hasErrors()) {
			throw new EntityResponseStatusException(bindingResult.getAllErrors(),
					HttpStatus.UNPROCESSABLE_ENTITY);
		}

		entry.setCreated(Instant.now().plus(getEntryDelay(), ChronoUnit.MINUTES));
		entry.setAuthor(getCurrentUsername());
		entry.setEntryStatus(EntryStatus.NORMAL);
		entry.setFlaggedStatus(FlaggedStatus.NORMAL);
		entry.setTitle(entry.getTitle().trim());
		entry.setText(entry.getText().trim());
		entry = entryManager.save(entry);

		Vote vote = new Vote();
		vote.setTarget(entry.getId());
		vote.setType(VoteType.up);
		vote.setTargetType(Types.entry);
		vote.setAuthor(getCurrentUsername());
		voteManager.save(vote);

		List<String> ignore = ignoreParameter.orElse(Lists.newArrayList());
		entryManager.applyMetadata(getCurrentUsername(), userManager.getKarma(getCurrentUsername()),
				entry, ignore);

		return entry;
	}

	/**
	 * Update entry.
	 *
	 * @param entry           the entry
	 * @param ignoreParameter the ignore parameter
	 * @return the entry
	 */
	@PreAuthorize("isAuthenticated()")
	@PatchMapping
	public Entry updateEntry(@RequestBody Entry entry,
			@RequestParam("ignore") Optional<List<String>> ignoreParameter) {
		Entry orgEntry = entryManager.get(entry.getId());
		if (orgEntry == null || !orgEntry.getAuthor().equals(getCurrentUsername()) || orgEntry
				.getCreated().plus(getEntryDelay(), ChronoUnit.MINUTES).isBefore(Instant.now())) {
			throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
		}

		RequestBodyErrors bindingResult = new RequestBodyErrors(entry);
		entryValidator.validate(entry, bindingResult);

		if (bindingResult.hasErrors()) {
			throw new EntityResponseStatusException(bindingResult.getAllErrors(),
					HttpStatus.UNPROCESSABLE_ENTITY);
		}

		orgEntry.setUrl(entry.getUrl());
		orgEntry.setTitle(entry.getTitle().trim());
		orgEntry.setText(entry.getText().trim());
		orgEntry.setTags(entry.getTags());
		orgEntry = entryManager.save(orgEntry);

		List<String> ignore = ignoreParameter.orElse(Lists.newArrayList());
		entryManager.applyMetadata(getCurrentUsername(), userManager.getKarma(getCurrentUsername()),
				orgEntry, ignore);
		return orgEntry;
	}

	/**
	 * Detele entry.
	 *
	 * @param id the id
	 */
	@PreAuthorize("isAuthenticated()")
	@DeleteMapping("{id}")
	public void deteleEntry(@PathVariable("id") Long id) {
		Entry orgEntry = entryManager.get(id);
		if (orgEntry == null || !orgEntry.getAuthor().equals(getCurrentUsername()) || orgEntry
				.getCreated().plus(getEntryDelay(), ChronoUnit.MINUTES).isBefore(Instant.now())) {
			throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
		}

		entryManager.delete(orgEntry);
	}

	/**
	 * Gets the title.
	 *
	 * @param url the url
	 * @return the title
	 */
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/helper/title")
	public String getTitle(@RequestParam("url") String url) {
		InputStream response = null;
		Scanner scanner = null;
		try {
			response = new URL(URLDecoder.decode(url, "utf-8")).openStream();
			scanner = new Scanner(response);
			String responseBody = scanner.useDelimiter("\\A").next();
			return responseBody.substring(responseBody.indexOf("<title>") + 7,
					responseBody.indexOf("</title>"));
		} catch (IOException ex) {
		} finally {
			try {
				if (response != null) {
					response.close();
				}
				if (scanner != null) {
					scanner.close();
				}
			} catch (IOException ex) {
			}
		}
		return "";
	}

}
