/*
 * Copyright (c) 2017, Tony Opara
 *        All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice, this 
 *   list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice, 
 *   this list of conditions and the following disclaimer in the documentation 
 *   and/or other materials provided with the distribution.
 * 
 * Neither the name of Google nor the names of its contributors may be used to 
 * endorse or promote products derived from this software without specific 
 * prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.google.refine.extension.database.cmd;

import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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

import com.fasterxml.jackson.core.JsonGenerator;
import org.apache.hc.core5.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.refine.extension.database.DatabaseConfiguration;
import com.google.refine.extension.database.DatabaseUtils;
import com.google.refine.util.ParsingUtilities;

public class SavedConnectionCommand extends DatabaseCommand {

    private static final Logger logger = LoggerFactory.getLogger("SavedConnectionCommand");

    private static final Pattern CONN_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9._-]*");
    private static final Pattern DATABASE_PORT_PATTERN = Pattern.compile("^[0-9]*");

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        if (logger.isDebugEnabled()) {
            logger.debug("SavedConnectionCommand::Get::connectionName::{}", request.getParameter("connectionName"));
        }

        String connectionName = request.getParameter("connectionName");
        try {
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-Type", "application/json");
            if (connectionName == null || connectionName.isEmpty()) {
                writeSavedConnectionResponse(response);
            } else {

                DatabaseConfiguration savedConnection = DatabaseUtils.getSavedConnection(connectionName);
                writeSavedConnectionResponse(response, savedConnection);

            }

        } catch (Exception e) {
            logger.error("Exception while loading settings {}", e);
        }
    }

    @Override
    public void doDelete(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        if (logger.isDebugEnabled()) {
            logger.debug("SavedConnectionCommand::Delete Connection: {}", request.getParameter("connectionName"));
        }

        String connectionName = request.getParameter("connectionName");

        DatabaseConfiguration savedConn = DatabaseUtils.getSavedConnection(connectionName);
        if (savedConn == null) {
            // logger.error("Connection With name:: {} does not exist!", request.getParameter("connectionName"));
            response.sendError(HttpStatus.SC_BAD_REQUEST, "Connection with name " + connectionName + " does not exists!");
            response.flushBuffer();
            return;
        }

        try {

            DatabaseUtils.deleteSavedConnections(connectionName);
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-Type", "application/json");

            writeSavedConnectionResponse(response);

        } catch (Exception e) {
            logger.error("Exception while Deleting Connection with name: {}, error:{}", connectionName, e);
        }
    }

    /**
     * 
     * @param response
     * @param savedConnection
     * @throws IOException
     * @throws JSONException
     */
    private void writeSavedConnectionResponse(HttpServletResponse response, DatabaseConfiguration savedConnection) throws IOException {
        Writer w = response.getWriter();
        try {
            JsonGenerator writer = ParsingUtilities.mapper.getFactory().createGenerator(w);

            writer.writeStartObject();
            writer.writeArrayFieldStart(DatabaseUtils.SAVED_CONNECTION_KEY);

            writer.writeStartObject();

            writer.writeStringField("connectionName", savedConnection.getConnectionName());
            writer.writeStringField("databaseType", savedConnection.getDatabaseType());
            writer.writeStringField("databaseHost", savedConnection.getDatabaseHost());
            writer.writeNumberField("databasePort", savedConnection.getDatabasePort());
            writer.writeStringField("databaseName", savedConnection.getDatabaseName());

            String dbPasswd = savedConnection.getDatabasePassword();
            if (dbPasswd != null && !dbPasswd.isEmpty()) {
                dbPasswd = DatabaseUtils.decrypt(savedConnection.getDatabasePassword());
            }
            writer.writeStringField("databasePassword", dbPasswd);
            writer.writeStringField("databaseSchema", savedConnection.getDatabaseSchema());
            writer.writeStringField("databaseUser", savedConnection.getDatabaseUser());

            writer.writeEndObject();
            writer.writeEndArray();

            writer.writeEndObject();
            writer.flush();
            writer.close();

        } finally {
            w.flush();
            w.close();
        }

    }

    /**
     * 
     * @param response
     * @throws IOException
     * @throws JSONException
     */
    private void writeSavedConnectionResponse(HttpServletResponse response) throws IOException {
        Writer w = response.getWriter();
        try {

            List<DatabaseConfiguration> savedConnections = DatabaseUtils.getSavedConnections();
            JsonGenerator writer = ParsingUtilities.mapper.getFactory().createGenerator(w);

            writer.writeStartObject();
            writer.writeArrayFieldStart(DatabaseUtils.SAVED_CONNECTION_KEY);

            int size = savedConnections.size();

            for (int i = 0; i < size; i++) {

                writer.writeStartObject();
                DatabaseConfiguration dbConfig = (DatabaseConfiguration) savedConnections.get(i);

                writer.writeStringField("connectionName", dbConfig.getConnectionName());

                writer.writeStringField("databaseType", dbConfig.getDatabaseType());

                writer.writeStringField("databaseHost", dbConfig.getDatabaseHost());

                writer.writeNumberField("databasePort", dbConfig.getDatabasePort());

                writer.writeStringField("databaseName", dbConfig.getDatabaseName());

                String dbPasswd = dbConfig.getDatabasePassword();
                if (dbPasswd != null && !dbPasswd.isEmpty()) {
                    dbPasswd = DatabaseUtils.decrypt(dbConfig.getDatabasePassword());
                }
                // writer.value(dbConfig.getDatabasePassword());
                writer.writeStringField("databasePassword", dbPasswd);

                writer.writeStringField("databaseSchema", dbConfig.getDatabaseSchema());

                writer.writeStringField("databaseUser", dbConfig.getDatabaseUser());

                writer.writeEndObject();

            }
            writer.writeEndArray();
            writer.writeEndObject();
            writer.flush();
            writer.close();
            // logger.info("Saved Connection Get Response sent");
        } finally {
            w.flush();
            w.close();
        }
    }

    /**
     * Add a new Saved JDBC connection configuration
     */
    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        if (!hasValidCSRFToken(request)) {
            respondCSRFError(response);
            return;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("doPost Connection: {}", request.getParameter("connectionName"));
        }

        DatabaseConfiguration jdbcConfig = getJdbcConfiguration(request);

        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-Type", "application/json");

        if (jdbcConfig.getConnectionName() == null) {
            response.sendError(HttpStatus.SC_BAD_REQUEST, "Connection Name is Required!");
            response.flushBuffer();
            return;
        }

        if (!validateInput(jdbcConfig.getConnectionName(), CONN_NAME_PATTERN)) {
            logger.warn("Invalid Connection Name: {}", jdbcConfig.getConnectionName());
            response.sendError(HttpStatus.SC_BAD_REQUEST, "Connection Name is Invalid. Expecting [a-zA-Z0-9._-]");
            response.flushBuffer();
            return;
        }

        if (!validateInput("" + jdbcConfig.getDatabasePort(), DATABASE_PORT_PATTERN)) {
            logger.warn("Invalid Database Port: {}", jdbcConfig.getDatabasePort());
            response.sendError(HttpStatus.SC_BAD_REQUEST, "Database Port Invalid. Expecting Numeric values only");
            response.flushBuffer();
            return;
        }

        DatabaseConfiguration savedConn = DatabaseUtils.getSavedConnection(jdbcConfig.getConnectionName());
        if (savedConn != null) {
            response.sendError(HttpStatus.SC_BAD_REQUEST, "Connection with name " + jdbcConfig.getConnectionName() + " already exists!");
            response.flushBuffer();
            return;
        }

        if (jdbcConfig.getDatabasePassword() != null) {
            // logger.debug("SavedConnectionCommand::Post::password::{}", jdbcConfig.getDatabasePassword());
            jdbcConfig.setDatabasePassword(DatabaseUtils.encrypt(jdbcConfig.getDatabasePassword()));
        }

        DatabaseUtils.addToSavedConnections(jdbcConfig);

        try {
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-Type", "application/json");

            writeSavedConnectionResponse(response);
        } catch (Exception e) {
            logger.error("Exception while loading settings {}", e);
        }

    }

    private boolean validateInput(String input, Pattern pattern) {
        Matcher matcher = pattern.matcher(input);
        if (matcher.matches()) {
            return true;
        }
        return false;
    }

    @Override
    public void doPut(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        if (logger.isDebugEnabled()) {
            logger.debug("databaseType::{} ", request.getParameter("databaseHost"));
        }
        // logger.info("databaseHost::{} " , request.getParameter("databaseServer"));

        DatabaseConfiguration jdbcConfig = getJdbcConfiguration(request);
        StringBuilder sb = new StringBuilder();
        boolean error = false;
        if (jdbcConfig.getConnectionName() == null) {
            sb.append("Connection Name, ");
            error = true;
        }
        if (jdbcConfig.getDatabaseHost() == null) {
            sb.append("Database Host, ");
            error = true;
        }
        if (jdbcConfig.getDatabaseUser() == null) {
            sb.append("Database User, ");
            error = true;
        }
        if (jdbcConfig.getDatabaseName() == null) {
            sb.append("Database Name, ");
            error = true;
        }
        if (error) {
            sb.append(" is missing");
            logger.debug("Connection Parameter errors::{}", sb.toString());
            response.sendError(HttpStatus.SC_BAD_REQUEST, sb.toString());
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Connection Config:: {}", jdbcConfig.getConnectionName());
        }

        if (jdbcConfig.getDatabasePassword() != null) {
            jdbcConfig.setDatabasePassword(DatabaseUtils.encrypt(jdbcConfig.getDatabasePassword()));
        }

        DatabaseUtils.editSavedConnections(jdbcConfig);

        try {
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-Type", "application/json");

            writeSavedConnectionResponse(response);

        } catch (Exception e) {
            logger.error("Exception while loading settings {}", e);
        }
    }

}
