package com.google.sps.data;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.FilterPredicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
import com.google.sps.data.Book;
import com.google.sps.data.Review;
import com.google.sps.data.User;
import java.util.Set;

public class ReviewDaoDatastore implements ReviewDao {
  private DatastoreService datastore;
  private static final String ENTITY_KIND = "Review";
  private static final String BOOK_PROPERTY = "Book";
  private static final String ISBN_PROPERTY = "ISBN";
  private static final String FULLTEXT_PROPERTY = "FullText";
  private static final String USER_PROPERTY = "User";
  private static final String USEREMAIL_PROPERTY = "UserEmail";

  public ReviewDaoDatastore() {
    datastore = DatastoreServiceFactory.getDatastoreService();
  }

  /**
   * Takes in a book and uploads all associated reviews
   * to Datastore with user set to default values
   *
   * @param book: Book object whose reviews are to be uploaded
   */
  @Override
  public void uploadAll(Book book) {
    User defaultUser = User.create("unknown email", "GoodReads User");
    for (String reviewStr : book.reviews()) {
      Review review = Review.builder().fullText(reviewStr).book(book).user(defaultUser).build();
      datastore.put(reviewToEntity(review));
    }
  }

  /**
   * Takes in a review and uploads to Datastore
   *
   * @param review: Review object to be uploaded
   */
  @Override
  public void uploadNew(Review review) {
    datastore.put(reviewToEntity(review));
  }

  /**
   * Takes in a book and returns a set of all Reviews associated
   * with that book in Datastore
   *
   * @param book: Book object whose reviews are to be returned
   * @return ImmutableSet of reviews associated with the book
   */
  @Override
  public ImmutableSet<Review> getAllByISBN(String isbn) {
    return getAllByProperty(ISBN_PROPERTY, isbn);
  }

  @Override
  public ImmutableSet<Review> getAllByEmail(String email) {
    return getAllByProperty(USEREMAIL_PROPERTY, email);
  }

  private static ImmutableSet<Review> getAllByProperty(String property, String value) {
    ImmutableSet.Builder<Review> reviews = new ImmutableSet.Builder<Review>();
    Filter filter = null;
    if (property.equals(ISBN_PROPERTY)) {
      filter = new FilterPredicate(ISBN_PROPERTY, FilterOperator.EQUAL, value);
    } else {
      filter = new FilterPredicate(USEREMAIL_PROPERTY, FilterOperator.EQUAL, value);
    }
    Query query = new Query(ENTITY_KIND).setFilter(filter);
    PreparedQuery results = datastore.prepare(query);
    if (results == null) {
      return reviews.build();
    }
    for (Entity entity : results.asIterable()) {
      Review review = entityToReview(entity);
      reviews.add(review);
    }
    return reviews.build();
  }

  private static Review entityToReview(Entity reviewEntity) {
    return Review.builder()
        .fullText((String) reviewEntity.getProperty(FULLTEXT_PROPERTY))
        .book((Book) reviewEntity.getProperty(BOOK_PROPERTY))
        .user((User) reviewEntity.getProperty(USER_PROPERTY))
        .build();
  }

  private static Entity reviewToEntity(Review review) {
    String isbn = review.book().isbn();
    String email = review.user().email();
    Entity reviewEntity = new Entity(createKey(isbn, email));

    reviewEntity.setProperty(FULLTEXT_PROPERTY, review.fullText());
    reviewEntity.setProperty(ISBN_PROPERTY, isbn);
    reviewEntity.setProperty(USEREMAIL_PROPERTY, email);
    return userEntity;
  }

  private Key createKey(String isbn, String email) {
    String uniqueID = isbn + "+" + email;
    return KeyFactory.createKey(ENTITY_KIND, uniqueID);
  }
}

@@ -791,24 +791,52 @@
 .data.User;%0A
+import java.lang.Exception;%0A
 import java.
@@ -2050,24 +2050,228 @@
 w review) %7B%0A
+    if (reviewExists(review.book().isbn(), review.user().email())) %7B%0A      throw new Exception(%22Review exists for book:%22 + review.book().title() + %22 by user %22%0A          + review.user().nickname());%0A    %7D%0A
     datastor
@@ -4429,11 +4429,257 @@
 ID);%0A  %7D
+%0A%0A  private boolean reviewExists(String isbn, String email) %7B%0A    Entity reviewEntity;%0A    try %7B%0A      reviewEntity = datastore.get(createKey(isbn, email));%0A    %7D catch (EntityNotFoundException ex) %7B%0A      return false;%0A    %7D%0A    return true;%0A  %7D
 %0A%7D%0A

allow one review per book+user