package eu.mulk.mulkcms2.benki.newsletter;

import static java.util.stream.Collectors.partitioningBy;
import static java.util.stream.Collectors.toList;

import eu.mulk.mulkcms2.benki.bookmarks.Bookmark;
import eu.mulk.mulkcms2.benki.lazychat.LazychatMessage;
import eu.mulk.mulkcms2.benki.posts.Post;
import io.quarkus.mailer.MailTemplate.MailTemplateInstance;
import io.quarkus.panache.common.Sort;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateExtension;
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.Dependent;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.annotation.CheckForNull;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hibernate.Session;

@Dependent
public class NewsletterSender {

  private static final DateTimeFormatter humanDateFormatter =
      DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);

  @ConfigProperty(name = "mulkcms.newsletter.time-zone")
  ZoneId newsletterTimeZone;

  @ConfigProperty(name = "quarkus.mailer.from")
  String senderAddress;

  @PersistenceContext EntityManager em;

  @CheckedTemplate
  static class Templates {
    public static native MailTemplateInstance newsletter(
        Newsletter newsletter, List<Bookmark> bookmarks, List<LazychatMessage> lazychatMessages);
  }

  @Scheduled(cron = "0 0 0 ? * Mon")
  @Transactional
  void run() throws InterruptedException, TimeoutException, ExecutionException {
    var session = em.unwrap(Session.class);

    List<Post<?>> posts =
        Post.find(
                """
                SELECT DISTINCT p FROM Post p
                  JOIN p.targets r
                  JOIN r.tags tag
                 WHERE p.newsletter IS NULL
                   AND p.scope = 'top_level'
                   AND tag = 'world'
                """,
                Sort.ascending("date"))
            .list();
    Post.fetchTexts(posts);

    if (posts.isEmpty()) {
      return;
    }

    var postsByClass = posts.stream().collect(partitioningBy(Post::isBookmark));
    var bookmarks =
        postsByClass.getOrDefault(Boolean.TRUE, List.of()).stream()
            .map(x -> (Bookmark) x)
            .collect(toList());
    var lazychatMessages =
        postsByClass.getOrDefault(Boolean.FALSE, List.of()).stream()
            .map(x -> (LazychatMessage) x)
            .collect(toList());

    var date = OffsetDateTime.now(newsletterTimeZone);
    var newsletterNumber =
        (int)
            session
                .createQuery("SELECT max(id) FROM Newsletter", Integer.class)
                .uniqueResultOptional()
                .map(x -> x + 1)
                .orElse(1);

    var newsletter = new Newsletter();
    newsletter.id = newsletterNumber;
    newsletter.date = date;
    newsletter.persist();

    posts.forEach(post -> post.newsletter = newsletter);

    var subscriberEmails =
        NewsletterSubscription.<NewsletterSubscription>stream("registrationKey IS NULL")
            .map(x -> x.email)
            .toArray(String[]::new);

    var mailText = Templates.newsletter(newsletter, bookmarks, lazychatMessages);
    var sendJob =
        mailText
            .subject(String.format("MulkCMS newsletter #%d", newsletterNumber))
            .to(senderAddress)
            .bcc(subscriberEmails)
            .send();
    sendJob.subscribe().asCompletionStage().get(10000, TimeUnit.SECONDS);
  }

  @TemplateExtension
  @CheckForNull
  static String humanDate(@CheckForNull LocalDate x) {
    if (x == null) {
      return null;
    }
    return humanDateFormatter.format(x);
  }

  @TemplateExtension
  @CheckForNull
  static String humanDate(@CheckForNull OffsetDateTime x) {
    if (x == null) {
      return null;
    }
    return humanDateFormatter.format(x);
  }
}
