package eu.dnetlib.lbs.controllers;

import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.regex.Pattern;

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import eu.dnetlib.lbs.LiteratureBrokerServiceConfiguration;
import eu.dnetlib.lbs.cron.ScheduledActions;
import eu.dnetlib.lbs.elasticsearch.NotificationRepository;
import eu.dnetlib.lbs.matchers.SubscriptionEventMatcher;
import eu.dnetlib.lbs.subscriptions.MapCondition;
import eu.dnetlib.lbs.subscriptions.NotificationFrequency;
import eu.dnetlib.lbs.subscriptions.NotificationMode;
import eu.dnetlib.lbs.subscriptions.Subscription;
import eu.dnetlib.lbs.subscriptions.SubscriptionRepository;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@RestController
@RequestMapping("/api/subscriptions")
@Api(tags = LiteratureBrokerServiceConfiguration.TAG_SUBSCRIPTIONS)
public class SubscriptionsController extends AbstractLbsController {

	@Autowired
	private SubscriptionRepository subscriptionRepo;

	@Autowired
	private NotificationRepository notificationRepo;

	@Autowired
	private ScheduledActions schedulerActions;

	@Autowired
	private SubscriptionEventMatcher subscriptionEventMatcher;

	public static final Predicate<String> verifyTopic = Pattern.compile("^[a-zA-Z0-9._-]+(\\/[a-zA-Z0-9._-]+)+$").asPredicate();
	public static final Predicate<String> verifyEmail = new Predicate<String>() {

		@Override
		public boolean test(final String email) {
			try {
				new InternetAddress(email).validate();
				return true;
			} catch (final AddressException e) {
				return false;
			}
		}

	};

	@ApiOperation("Return the list of subscriptions")
	@RequestMapping(value = "", method = RequestMethod.GET)
	public Iterable<Subscription> listSubscriptions() {
		return this.subscriptionRepo.findAll();
	}

	@ApiOperation("Return a subscription by ID")
	@RequestMapping(value = "/{id}", method = RequestMethod.GET)
	public Subscription getSubscription(@PathVariable final String id) {
		return this.subscriptionRepo.findById(id).get();
	}

	@ApiOperation("Delete a subscription by ID and its notifications")
	@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
	public void deleteSubscription(@PathVariable final String id) {
		this.subscriptionRepo.deleteById(id);
		this.notificationRepo.deleteBySubscriptionId(id);
	}

	@ApiOperation("Perform a new subscription")
	@RequestMapping(value = "", method = RequestMethod.POST)
	public Subscription registerSubscription(@RequestBody final InSubscription inSub) {
		final Subscription sub = inSub.asSubscription();
		this.subscriptionRepo.save(sub);
		return sub;
	}

	@ApiOperation("Delete all subscriptions and notifications")
	@RequestMapping(value = "", method = RequestMethod.DELETE)
	public Map<String, Object> clearSubscriptions() {
		final Map<String, Object> res = new HashMap<>();
		this.subscriptionRepo.deleteAll();
		this.notificationRepo.deleteAll();
		res.put("deleted", "all");
		return res;
	}

	@ApiOperation("Launch the thread that produces new notifications")
	@RequestMapping(value = "/startMatching", method = RequestMethod.GET)
	public List<String> startMatching() {
		this.schedulerActions.startSubscriptionEventsMatcher();
		return Arrays.asList("done");
	}

	@ApiOperation("Launch the thread that produces new notifications by subscriptuion id")
	@RequestMapping(value = "/startMatching/{subscriptionId}", method = RequestMethod.GET)
	public List<String> startMatching(@PathVariable final String subscriptionId) {
		final Optional<Subscription> s = this.subscriptionRepo.findById(subscriptionId);
		if (s.isPresent()) {
			this.subscriptionEventMatcher.startMatching(s.get());
			return Arrays.asList("done");
		} else {
			return Arrays.asList("subscription not present");
		}
	}

	@ApiOperation("Reset the last notification date")
	@RequestMapping(value = "/{id}/date", method = RequestMethod.DELETE)
	public void deleteNotificationDate(@PathVariable final String id) {
		final Subscription s = this.subscriptionRepo.findById(id).get();
		s.setLastNotificationDate(null);
		this.subscriptionRepo.save(s);
	}

	@ApiOperation("Reset all the last notification dates")
	@RequestMapping(value = "/resetLastNotificationDates", method = RequestMethod.GET)
	public void deleteAllNotificationDates() {
		for (final Subscription s : this.subscriptionRepo.findAll()) {
			s.setLastNotificationDate(null);
			this.subscriptionRepo.save(s);
		}
	}
}

class InSubscription {

	private String subscriber;
	private String topic;
	private NotificationFrequency frequency;
	private NotificationMode mode;
	private List<MapCondition> conditions;

	public InSubscription() {}

	public InSubscription(final String subscriber, final String topic, final NotificationFrequency frequency, final NotificationMode mode,
			final List<MapCondition> conditions) {
		this.subscriber = subscriber;
		this.topic = topic;
		this.frequency = frequency;
		this.mode = mode;
		this.conditions = conditions;
	}

	public String getSubscriber() {
		return this.subscriber;
	}

	public void setSubscriber(final String subscriber) {
		this.subscriber = subscriber;
	}

	public String getTopic() {
		return this.topic;
	}

	public void setTopic(final String topic) {
		this.topic = topic;
	}

	public NotificationFrequency getFrequency() {
		return this.frequency;
	}

	public void setFrequency(final NotificationFrequency frequency) {
		this.frequency = frequency;
	}

	public NotificationMode getMode() {
		return this.mode;
	}

	public void setMode(final NotificationMode mode) {
		this.mode = mode;
	}

	public List<MapCondition> getConditions() {
		return this.conditions;
	}

	public void setConditions(final List<MapCondition> conditions) {
		this.conditions = conditions;
	}

	public Subscription asSubscription() {
		if (StringUtils.isBlank(this.subscriber)) { throw new IllegalArgumentException("subscriber is empty"); }
		if (StringUtils.isBlank(this.topic)) { throw new IllegalArgumentException("topic is empty"); }

		if (!SubscriptionsController.verifyEmail.test(this.subscriber)) { throw new IllegalArgumentException("Invalid email: " + this.subscriber); }
		if (!SubscriptionsController.verifyTopic.test(this.topic)) { throw new IllegalArgumentException("Invalid topic: " + this.topic); }

		final String subscriptionId = "sub-" + UUID.randomUUID();

		return new Subscription(subscriptionId, this.subscriber, this.topic, this.frequency, this.mode, null, new Date(), this.conditions);
	}
}
