package com.uwetrottmann.seriesguide;

import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import com.google.api.server.spi.response.CollectionResponse;
import com.google.api.server.spi.response.UnauthorizedException;
import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.users.User;
import com.google.appengine.datanucleus.query.JPACursorHelper;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nullable;
import javax.inject.Named;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityNotFoundException;
import javax.persistence.EntityManager;
import javax.persistence.Query;

@Api(
        name = "shows",
        version = "v1",
        scopes = {Constants.EMAIL_SCOPE},
        clientIds = {com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID},
        namespace = @ApiNamespace(ownerDomain = "uwetrottmann.com", ownerName = "uwetrottmann.com",
                packagePath = "seriesguide")
)
public class ShowEndpoint {

    /**
     * This method lists all the entities inserted in datastore. It uses HTTP GET method and paging
     * support.
     *
     * @return A CollectionResponse class containing the list of all entities persisted and a cursor
     * to the next page.
     */
    @SuppressWarnings({"unchecked", "unused"})
    @ApiMethod(name = "listShow")
    public CollectionResponse<Show> listShow(
            @Nullable @Named("cursor") String cursorString,
            @Nullable @Named("limit") Integer limit) {

        EntityManager mgr = null;
        List<Show> execute = null;

        try {
            mgr = getEntityManager();
            Query query = mgr.createQuery("select from Show as Show");
            Cursor cursor;
            if (cursorString != null && cursorString.trim().length() > 0) {
                cursor = Cursor.fromWebSafeString(cursorString);
                query.setHint(JPACursorHelper.CURSOR_HINT, cursor);
            }

            if (limit != null) {
                query.setFirstResult(0);
                query.setMaxResults(limit);
            }

            execute = (List<Show>) query.getResultList();
            cursor = JPACursorHelper.getCursor(execute);
            if (cursor != null) {
                cursorString = cursor.toWebSafeString();
            }

            // Tight loop for fetching all entities from datastore and accomodate
            // for lazy fetch.
            for (Show obj : execute) {
                ;
            }
        } finally {
            if (mgr != null) {
                mgr.close();
            }
        }

        return CollectionResponse.<Show>builder()
                .setItems(execute)
                .setNextPageToken(cursorString)
                .build();
    }

    /**
     * This method gets the entity having primary key id. It uses HTTP GET method.
     */
    @ApiMethod(
            name = "get",
            path = "get/{tvdbid}"
    )
    public Show getShow(@Named("tvdbid") int tvdbId, User user) throws UnauthorizedException {
        Key key = Security.get()
                .createKey(Show.class.getSimpleName(), String.valueOf(tvdbId), user);

        EntityManager mgr = getEntityManager();
        Show show = null;
        try {
            show = mgr.find(Show.class, key);
        } finally {
            mgr.close();
        }
        return show;
    }

    /**
     * Inserts or updates the given show in(to) the Datastore.
     */
    @ApiMethod(
            name = "save",
            path = "save"
    )
    public ShowList saveShows(ShowList shows, User user) throws UnauthorizedException {
        // create user-specific keys for all shows
        for (Show show : shows.getShows()) {
            show.setKey(Security.get().createKey(Show.class.getSimpleName(),
                    String.valueOf(show.getTvdbId()), user));
        }

        // update existing shows
        Map<Integer, Show> existingShows = findAndUpdateExistingShows(shows.getShows(), user);

        // insert new shows
        List<Show> newShows = insertNewShows(shows.getShows(), existingShows.keySet(), user);

        // return all shows
        newShows.addAll(existingShows.values());
        shows.setShows(newShows);

        return shows;
    }

    private Map<Integer, Show> findAndUpdateExistingShows(List<Show> shows, User user)
            throws UnauthorizedException {
        Map<Integer, Show> existingShows = new HashMap<Integer, Show>();

        for (Show show : shows) {
            EntityManager mgr = getEntityManager();
            try {
                // show with this key already exists?
                Show existingShow = mgr.find(Show.class, show.getKey());
                if (existingShow != null) {
                    // set updated values
                    existingShow.copyPropertyValues(show);
                    existingShow.setUpdatedAt(new Date());

                    // save back to Datastore
                    mgr.merge(existingShow);

                    // flag as existing
                    existingShows.put(existingShow.getTvdbId(), existingShow);
                }
            } finally {
                mgr.close();
            }
        }

        return existingShows;
    }

    private List<Show> insertNewShows(List<Show> shows, Set<Integer> existingShows, User user) {
        List<Show> newShows = new LinkedList<Show>();

        for (Show show : shows) {
            if (existingShows.contains(show.getTvdbId())) {
                // already was updated
                continue;
            }

            // create metadata
            show.setCreatedAt(new Date());
            show.setUpdatedAt(show.getCreatedAt());
            show.setOwner(user.getEmail());

            // insert into Datastore
            EntityManager mgr = getEntityManager();
            try {
                mgr.persist(show);
            } finally {
                mgr.close();
            }

            newShows.add(show);
        }

        return newShows;
    }

    /**
     * This method is used for updating an existing entity. If the entity does not exist in the
     * datastore, an exception is thrown. It uses HTTP PUT method.
     *
     * @param show the entity to be updated.
     * @return The updated entity.
     */
    @ApiMethod(name = "updateShow")
    public Show updateShow(Show show) {
        EntityManager mgr = getEntityManager();
        try {
            if (!containsShow(show)) {
                throw new EntityNotFoundException("Object does not exist");
            }
            mgr.persist(show);
        } finally {
            mgr.close();
        }
        return show;
    }

    @ApiMethod(
            name = "remove",
            path = "remove/{tvdbid}"
    )
    public Show removeShow(@Named("tvdbid") int tvdbId, User user) throws UnauthorizedException {
        Key key = Security.get()
                .createKey(Show.class.getSimpleName(), String.valueOf(tvdbId), user);

        EntityManager mgr = getEntityManager();
        Show show = null;
        try {
            show = mgr.find(Show.class, key);
            mgr.remove(show);
        } finally {
            mgr.close();
        }
        return show;
    }

    private boolean containsShow(Show show) {
        EntityManager mgr = getEntityManager();
        boolean contains = true;
        try {
            Show item = mgr.find(Show.class, show.getKey());
            if (item == null) {
                contains = false;
            }
        } finally {
            mgr.close();
        }
        return contains;
    }

    private static EntityManager getEntityManager() {
        return EMF.get().createEntityManager();
    }

}

@@ -6324,655 +6324,8 @@
  %7D%0A%0A
-    /**%0A     * This method is used for updating an existing entity. If the entity does not exist in the%0A     * datastore, an exception is thrown. It uses HTTP PUT method.%0A     *%0A     * @param show the entity to be updated.%0A     * @return The updated entity.%0A     */%0A    @ApiMethod(name = %22updateShow%22)%0A    public Show updateShow(Show show) %7B%0A        EntityManager mgr = getEntityManager();%0A        try %7B%0A            if (!containsShow(show)) %7B%0A                throw new EntityNotFoundException(%22Object does not exist%22);%0A            %7D%0A            mgr.persist(show);%0A        %7D finally %7B%0A            mgr.close();%0A        %7D%0A        return show;%0A    %7D%0A%0A
     
@@ -6877,377 +6877,8 @@
  %7D%0A%0A
-    private boolean containsShow(Show show) %7B%0A        EntityManager mgr = getEntityManager();%0A        boolean contains = true;%0A        try %7B%0A            Show item = mgr.find(Show.class, show.getKey());%0A            if (item == null) %7B%0A                contains = false;%0A            %7D%0A        %7D finally %7B%0A            mgr.close();%0A        %7D%0A        return contains;%0A    %7D%0A%0A
     

Drop update show endpoint, remove unused contains method.