/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.samples.apps.iosched.server.schedule.server.servlet;

import com.google.appengine.api.mail.MailService.Message;
import com.google.appengine.api.mail.MailServiceFactory;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.stream.JsonWriter;
import com.google.samples.apps.iosched.server.schedule.Config;
import com.google.samples.apps.iosched.server.schedule.model.JsonDataSource;
import com.google.samples.apps.iosched.server.schedule.model.JsonDataSources;
import com.google.samples.apps.iosched.server.schedule.server.cloudstorage.CloudFileManager;
import com.google.samples.apps.iosched.server.schedule.server.input.VendorDynamicInput;

import java.io.IOException;
import java.io.Writer;
import java.nio.channels.Channels;
import java.util.HashSet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet that runs the Updater flow. Can be called from a cron job or directly.
 * The parameter force="true" forces a new update, even if the API session data has
 * not changed since the last run.
 *
 */
public class CMSUpdateServlet extends HttpServlet {

  private static final HashSet<String> nonAdminUsers = new HashSet<String>();
  static {
    nonAdminUsers.add("ldale@google.com");
  }

  private final UserService userService = UserServiceFactory.getUserService();

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws IOException {
    if ("true".equals(req.getParameter("cron"))) {
      // Request is triggered by cron script, treat as POST
      doPost(req, resp);
      return;
    }

    if (!performBasicChecking(req, resp)) {
      return;
    }
    boolean showOnly = "true".equals(req.getParameter("show"));
    if (showOnly) {
      process(resp, true);
    } else {
      redirectToConfirmationPage(req, resp);
    }
  }

  @Override
  public void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws IOException {
    if (!performBasicChecking(req, resp)) {
      return;
    }
    String userConfirmation = req.getParameter("user_confirmation");
    if (userConfirmation == null) {
      redirectToConfirmationPage(req, resp);
      return;
    }
    process(resp, false);

  }
  private void process(HttpServletResponse resp, boolean showOnly) throws IOException {
    // everything ok, let's update
    StringBuilder summary = new StringBuilder();
    JsonObject contents = new JsonObject();
    JsonDataSources sources = new VendorDynamicInput().fetchAllDataSources();
    for (String entity: sources) {
      JsonArray array = new JsonArray();
      JsonDataSource source = sources.getSource(entity);
      for (JsonObject obj: source) {
        array.add(obj);
      }
      summary.append(entity).append(": ").append(source.size()).append("\n");
      contents.add(entity, array);
    }

    if (showOnly) {
      // Show generated contents to the output
      resp.setContentType("application/json");
      Writer writer = Channels.newWriter(Channels.newChannel(resp.getOutputStream()), "UTF-8");
      JsonWriter outputWriter = new JsonWriter(writer);
      outputWriter.setIndent("  ");
      new Gson().toJson(contents, outputWriter);
      outputWriter.flush();

    } else {
      // Write file to cloud storage
      CloudFileManager fileManager = new CloudFileManager();
      fileManager.createOrUpdate("__raw_session_data.json", contents, true);

      // send email
      Message message = new Message();
      message.setSender(Config.EMAIL_FROM);
      message.setSubject("[iosched-data-update] Manual sync from CMS");
      message.setTextBody(
          "Hey,\n\n"
          + "(this message is autogenerated)\n"
          + "This is a heads up that "+userService.getCurrentUser().getEmail()+" has just updated the IOSched 2015 data from the Vendor CMS.\n\n"
              + "Here is a brief status of what has been extracted from the Vendor API:\n"
              + summary
              + "\n\n"
              + "If you want to check the most current data that will soon be sync'ed to the IOSched Android app, "
              + "check this link: http://storage.googleapis.com/iosched-updater-dev.appspot.com/__raw_session_data.json\n"
              + "This data will remain unchanged until someone with proper privileges updates it again on https://iosched-updater-dev.appspot.com/cmsupdate\n\n"
              + "Thanks!\n\n"
              + "A robot on behalf of the IOSched team!\n\n"
              + "PS: you are receiving this either because you are an admin of the IOSched project or "
              + "because you are in a hard-coded list of I/O organizers. If you don't want to "
              + "receive it anymore, pay me a beer and ask kindly.");
      MailServiceFactory.getMailService().sendToAdmins(message);

      resp.sendRedirect("/admin/schedule/updateok.html");
    }
  }

  private void redirectToConfirmationPage(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    try {
      req.getRequestDispatcher("/admin/schedule/cmsupdate.html").forward(req, resp);
    } catch (ServletException e) {
      throw new RuntimeException(e);
    }
  }

  private boolean performBasicChecking(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    if (!userService.isUserLoggedIn()) {
      resp.sendRedirect(userService.createLoginURL(req.getRequestURI()));
      return false;
    }
    if (!isValidUser()) {
      resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Sorry, your user has no permission. If you think you should have, you know who to contact to get it.");
      return false;
    }
    // Ignore cron when running in production.
    if ("true".equals(req.getParameter("cron")) && !Config.STAGING) {
      return false;
    }
    return true;
  }

  private boolean isValidUser() {
    return userService.isUserLoggedIn() &&
        ( userService.isUserAdmin() ||
            nonAdminUsers.contains(userService.getCurrentUser().getEmail()));
  }
}
