package org.wso2.carbon.device.mgt.ios.dep.utils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpStatus;
import org.wso2.carbon.device.mgt.common.DeviceManagementException;
import org.wso2.carbon.device.mgt.ios.dep.DEPConstants;
import org.wso2.carbon.device.mgt.ios.dep.exception.AppleDEPConnectorException;
import org.wso2.carbon.device.mgt.ios.dep.exception.DEPServerTokenException;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;

/**
 * This class represents methods which are used to setup HTTP connection related details
 * which are used to access Apple DEP endpoints and handling of such connections exceptions.
 */
class WebTransportUtils {

    private static final Log log = LogFactory.getLog(WebTransportUtils.class);

    /**
     * Opens up a HTTP connection for the requested URL.
     * @param url requesting URL.
     * @return opened up HTTP connection.
     * @throws AppleDEPConnectorException if the method encounters any Apple DEP related issue.
     */
    static HttpURLConnection getHttpConnection(String url) throws AppleDEPConnectorException {
        URL connectionUrl = null;
        HttpURLConnection httpConnection = null;
        try {
            connectionUrl = new URL(url);
            httpConnection = (HttpURLConnection) connectionUrl.openConnection();
        } catch (MalformedURLException e) {
            throw new AppleDEPConnectorException("Error occurred whilst trying to form HTTP-URL " +
                                                 "from string: " + url, e);
        } catch (IOException e) {
            throw new AppleDEPConnectorException("Error occurred whilst trying to open a " +
                                                 "connection to: " + url, e);
        }
        return httpConnection;
    }

    /**
     * Checks the executed HTTP connection is successful and return appropriate response.
     * @param httpConnection executed HTTP connection.
     * @return response returned by the executed HTTP connection.
     * @throws AppleDEPConnectorException if the method encounters any Apple DEP related issue.
     */
    static byte[] getResponseBytes(HttpURLConnection httpConnection) throws
                                                                     AppleDEPConnectorException {
        WebTransportUtils.checkHttpConnection(httpConnection);
        InputStream responseStream = null;
        byte[] responseBytes;
        try {
            if (WebTransportUtils.getHttpResponseCode(httpConnection) == HttpStatus.SC_OK) {
                responseStream = httpConnection.getInputStream();
            } else {
                responseStream = httpConnection.getErrorStream();
            }
            responseBytes = new byte[responseStream.available()];
            responseStream.read(responseBytes);
            return responseBytes;
        } catch (IOException e) {
            throw new AppleDEPConnectorException("An IO error occurred whilst trying to get the " +
                                                 "response code from: " + httpConnection.getURL() +
                                                 " for a " + httpConnection.getRequestMethod() +
                                                 "method.", e);
        } finally {
            if (responseStream != null) {
                try {
                    responseStream.close();
                } catch (IOException e) {
                    //do nothing
                }
            }
        }
    }

    /**
     * Creates the HTTP connection with provided configurations.
     * @param requestURL requesting URL. (Apple DEP endpoints)
     * @param requestMethod request method. (GET/PUT/POST/DELETE)
     * @param contentType content type of the request. (application/json)
     * @return HTTP connection containing all the required details for the execution.
     * @throws AppleDEPConnectorException if the method encounters any Apple DEP related issue.
     * @throws DEPServerTokenException if the server token (.p7m) is expired.
     * @throws DeviceManagementException if iOS DEP configuration is not able to retrieve
     */
    static HttpURLConnection createHttpConnection(String requestURL, String requestMethod,
                                                  String contentType) throws
                                                                      AppleDEPConnectorException,
                                                                      DEPServerTokenException,
                                                                      DeviceManagementException {
        HttpURLConnection httpConnection = WebTransportUtils.getHttpConnection(requestURL);
        try {
            httpConnection.setRequestMethod(requestMethod);
            httpConnection.setRequestProperty("User-Agent", DEPConstants.USER_AGENT);
            httpConnection.setRequestProperty("X-Server-Protocol-Version",
                                              DEPConstants.PROTOCOL_VERSION);
            if (contentType != null) {
                httpConnection.setRequestProperty("Content-Type", contentType);
            }
            httpConnection.setRequestProperty("X-ADM-Auth-Session",
                                              SessionTokenUtils.generateSessionToken());
        } catch (ProtocolException e) {
            throw new AppleDEPConnectorException("Protocol specific error occurred when trying " +
                                                 "to set method to " + requestMethod + " for:" +
                                                 requestURL, e);
        }
        return httpConnection;
    }

    /**
     * Sets up HTTP request body for the provided HTTP connection.
     * @param httpConnection already set up (via "createHttpConnection" method) HTTP connection.
     * @param pushDataPayload payload contains the data which needs to be sent via the HTTP request.
     * @throws AppleDEPConnectorException if the method encounters any Apple DEP related issue.
     */
    static void setHttpRequestBody(HttpURLConnection httpConnection, String pushDataPayload)
            throws AppleDEPConnectorException {
        WebTransportUtils.checkHttpConnection(httpConnection);
        httpConnection.setDoOutput(true);
        try (DataOutputStream dataBody = new DataOutputStream((httpConnection.getOutputStream()))) {
            dataBody.writeBytes(pushDataPayload);
        } catch (IOException e) {
            throw new AppleDEPConnectorException("Input output issue occurred when setting payload of "
                                                 + pushDataPayload + " to request body for " +
                                                 httpConnection.getURL(), e);
        }
    }

    /**
     * Checks the executed HTTP request and returns the HTTP response code for the executed request.
     * @param httpConnection executed HTTP connection
     * @return response code of the executed HTTP request. (200/404/400)
     * @throws AppleDEPConnectorException if the method encounters any Apple DEP related issue.
     */
    static int getHttpResponseCode(HttpURLConnection httpConnection) throws
                                                                     AppleDEPConnectorException {
        WebTransportUtils.checkHttpConnection(httpConnection);
        int responseCode = DEPConstants.DEFAULT_RESPONSE_CODE;
        try {
            responseCode = httpConnection.getResponseCode();
        } catch (IOException e) {
            throw new AppleDEPConnectorException("Input output issue occurred when getting " +
                                                 "request response code " + "from " +
                                                 httpConnection.getURL(), e);
        }
        return responseCode;
    }

    /**
     * Handle all the Apple DEP endpoints related generic exceptions and provide appropriate
     * messages for causing the exception.
     * @param httpConnection executed HTTP connection.
     * @param responseBytes response give by the HTTP request.
     * @throws AppleDEPConnectorException if the method encounters any Apple DEP related issue.
     */
    static void handleWebException(HttpURLConnection httpConnection, byte[] responseBytes)
            throws AppleDEPConnectorException {
        WebTransportUtils.checkHttpConnection(httpConnection);
        String message = "";
        String errorMessage = new String(responseBytes);
        int responseCode = WebTransportUtils.getHttpResponseCode(httpConnection);
        if (responseCode == HttpStatus.SC_OK) {
            return;
        }
        switch (responseCode) {
            case HttpStatus.SC_BAD_REQUEST:
                message = "Bad request error occurred due to invalid JSON body for "
                          + httpConnection.getURL();
                break;
            case HttpStatus.SC_UNAUTHORIZED:
                message = "Unauthorized access due to authentication token has expired "
                          + httpConnection.getURL();
                break;
            case HttpStatus.SC_FORBIDDEN:
                message = "Forbidden due to authentication token is invalid while " +
                          "trying to access " + httpConnection.getURL();
                break;
            case HttpStatus.SC_METHOD_NOT_ALLOWED:
                message = "Invalid HTTP method for accessing " + httpConnection.getURL();
                break;
            default:
                message = "Request for " + httpConnection.getURL() + " was not successful due to "
                          + errorMessage;
                break;
        }
        throw new AppleDEPConnectorException(message);
    }

    /**
     * Handle all the Apple DEP endpoints related specific exceptions and provides appropriate
     * messages for exception caused.
     * @param responseBytes response given by the HTTP request.
     * @param httpConnection executed HTTP connection.
     * @throws AppleDEPConnectorException if the method encounters any Apple DEP related issue.
     */
    static void handleSpecificWebException(byte[] responseBytes,
                                HttpURLConnection httpConnection) throws
                                                                  AppleDEPConnectorException {
        WebTransportUtils.checkHttpConnection(httpConnection);
        int responseCode = WebTransportUtils.getHttpResponseCode(httpConnection);
        if (responseCode == HttpStatus.SC_OK) {
            return;
        }
        if (responseCode == HttpStatus.SC_BAD_REQUEST || responseCode == HttpStatus.SC_NOT_FOUND) {
            String errorMessage = "";
            switch (new String(responseBytes)) {
                case "DEVICE_ID_REQUIRED":
                    errorMessage = "the request did not contain any device serial numbers.";
                    break;
                case "PROFILE_UUID_REQUIRED":
                    errorMessage = "the request did not contain a profile UUID.";
                    break;
                case "NOT_FOUND":
                    errorMessage = "the profile specified by UUID is invalid.";
                    break;
                case "CONFIG_URL_REQUIRED":
                    errorMessage = "the MDM server URL is missing in the profile.";
                    break;
                case "CONFIG_NAME_REQUIRED":
                    errorMessage = "the configuration name is missing in the profile.";
                    break;
                case "FLAGS_INVALID":
                    errorMessage = "the invalid flags. 'is_mdm_removable' can be set to " +
                                   "false only if 'is_supervised' is set to true.";
                    break;
                case "CONFIG_URL_INVALID":
                    errorMessage = "the URL field of the uploaded profile is either empty or " +
                                   "has exceeded the maximum allowed length " +
                                   "(2000 URL encoded characters).";
                    break;
                case "CONFIG_NAME_INVALID":
                    errorMessage = "the name field of the uploaded profile is either" +
                                   " empty or has exceeded the maximum allowed length " +
                                   "(125 UTF-8 characters).";
                    break;
                case "DEPARTMENT_INVALID":
                    errorMessage = "the department field of the uploaded profile is either " +
                                   "empty or has exceeded the maximum allowed length " +
                                   "(125 UTF-8 characters).";
                    break;
                case "SUPPORT_PHONE_INVALID":
                    errorMessage = "the support phone number field of the uploaded profile is " +
                                   "either empty or has exceeded the maximum allowed length " +
                                   "(50 UTF-8 characters).";
                    break;
                case "SUPPORT_EMAIL_INVALID":
                    errorMessage = "the support e-mail address field of the uploaded profile " +
                                   "is either empty or has exceeded the maximum allowed length " +
                                   "(250 UTF-8 characters).";
                    break;
                case "DESCRIPTION_INVALID":
                    errorMessage = "the description field of the uploaded profile is either " +
                                   "empty or has exceeded the maximum allowed length " +
                                   "(512 UTF-8 characters).";
                    break;
                case "MAGIC_INVALID":
                    errorMessage = "the magic field of the uploaded profile is either empty " +
                                   "or has exceeded the maximum allowed length " +
                                   "(256 UTF-8 characters).";
                    break;
                case "CURSOR_REQUIRED":
                    errorMessage = "the request did not contain any cursor value.";
                    break;
                case "INVALID_CURSOR":
                    errorMessage = "the request contains an invalid cursor value.";
                    break;
                case "EXPIRED_CURSOR":
                    errorMessage = "the request contains a cursor value which is older than 7 days.";
                    break;
                case "MALFORMED_REQUEST_BODY":
                    errorMessage = "the request contains malformed data (invalid JSON body).";
                    break;
                default:
                    errorMessage = new String(responseBytes);
                    break;
            }
            throw new AppleDEPConnectorException("The request to " + httpConnection.getURL()
                                                 + " failed due to " + errorMessage);
        }
        WebTransportUtils.handleWebException(httpConnection, responseBytes);
    }

    /**
     * Checks whther the connection is null and if so throws exception.
     * @param httpConnection HTTP connection.
     * @throws AppleDEPConnectorException if the method encounters any Apple DEP related issue.
     */
    static void checkHttpConnection(HttpURLConnection httpConnection)
            throws AppleDEPConnectorException {
        if (httpConnection == null) {
            throw new AppleDEPConnectorException("HTTP Connection is null.");
        }
    }
}
