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

import com.google.gson.Gson;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpStatus;
import org.json.JSONObject;
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.beans.Account;
import org.wso2.carbon.device.mgt.ios.dep.beans.Device;
import org.wso2.carbon.device.mgt.ios.dep.beans.DeviceProfile;
import org.wso2.carbon.device.mgt.ios.dep.beans.DeviceSerials;
import org.wso2.carbon.device.mgt.ios.dep.beans.DevicesWithProfileAssignmentStatus;
import org.wso2.carbon.device.mgt.ios.dep.beans.DevicesWithSyncInfo;
import org.wso2.carbon.device.mgt.ios.dep.beans.ProfileWithDevices;
import org.wso2.carbon.device.mgt.ios.dep.beans.ProfileAssignmentStatus;
import org.wso2.carbon.device.mgt.ios.dep.beans.ProfileAssignmentStatuses;
import org.wso2.carbon.device.mgt.ios.dep.beans.Profile;
import org.wso2.carbon.device.mgt.ios.dep.beans.DeviceDetailsFilter;
import org.wso2.carbon.device.mgt.ios.dep.beans.PaginationDetails;
import org.wso2.carbon.device.mgt.ios.dep.beans.ServerToken;
import org.wso2.carbon.device.mgt.ios.dep.exception.AppleDEPConnectorException;
import org.wso2.carbon.device.mgt.ios.dep.exception.DEPServerTokenException;

import javax.xml.bind.DatatypeConverter;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * This class represents methods through which Apple DEP endpoints are accessed
 * for retrieval of Apple DEP related information.
 */
public class EndpointUtils {

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

    /**
     * Provides details about the registered organization at the Apple DEP portal.
     * @return Account bean which contains organization details.
     * @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.
     */
    public static Account getAccountDetails() throws AppleDEPConnectorException,
                                                     DEPServerTokenException,
                                                     DeviceManagementException {
        HttpURLConnection httpConnection = null;
        try {
            httpConnection = WebTransportUtils.createHttpConnection(DEPConstants.ACCOUNT_DETAILS_URL,
                                                                    DEPConstants.HTTP_GET, null);
            byte[] responseBytes = WebTransportUtils.getResponseBytes(httpConnection);
            if (WebTransportUtils.getHttpResponseCode(httpConnection) != HttpStatus.SC_OK) {
                throw new AppleDEPConnectorException("The retrieving of account details of the " +
                                                     "organization from " +
                                                     httpConnection.getURL() + " was not " +
                                                     "successful.");
            }
            Gson gson = new Gson();
            return gson.fromJson(new String(responseBytes), Account.class);
        } finally {
            if (httpConnection != null) {
                httpConnection.disconnect();
            }
        }
    }

    /**
     * Fetches a list of all deviceSerials that are assigned to the MDM server at the time of the
     * request.
     * @param paginationDetails bean containing number of deviceSerials to be retrieved with an
     *                          optional cursor needed to fetch deviceSerials.
     * @return Device bean which contains fetched device details.
     * @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.
     */
    public static Device getDevices(PaginationDetails paginationDetails)
            throws AppleDEPConnectorException, DEPServerTokenException, DeviceManagementException {
        HttpURLConnection httpConnection = null;
        try {
            Gson gson = new Gson();
            String payload = gson.toJson(paginationDetails);
            httpConnection = WebTransportUtils.createHttpConnection(DEPConstants.FETCH_DEVICES_URL,
                                    DEPConstants.HTTP_POST, DEPConstants.APPLICATION_JSON_CHARSET);
            WebTransportUtils.setHttpRequestBody(httpConnection, payload);
            byte[] responseBytes = WebTransportUtils.getResponseBytes(httpConnection);
            WebTransportUtils.handleSpecificWebException(responseBytes, httpConnection);
            return gson.fromJson(new String(responseBytes), Device.class);
        } finally {
            if (httpConnection != null) {
                httpConnection.disconnect();
            }
        }
    }

    /**
     * The sync service depends on a cursor returned by the get device service.
     * It returns a list of all modifications (additions or deletions) since the specified cursor.
     * The cursor passed to this endpoint should not be older than 7 days.
     * @param syncDevice bean containing number of deviceSerials to be retrieved with a cursor
     *                   needed to fetch deviceSerials.
     * @return DevicesWithSyncInfo bean which contains synced device details.
     * @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.
     */
    public static DevicesWithSyncInfo syncDevices(PaginationDetails syncDevice)
            throws AppleDEPConnectorException, DEPServerTokenException, DeviceManagementException {
        HttpURLConnection httpConnection = null;
        try {
            Gson gson = new Gson();
            String payload = gson.toJson(syncDevice);
            httpConnection = WebTransportUtils.createHttpConnection(DEPConstants.SYNC_DEVICES_URL,
                                    DEPConstants.HTTP_POST, DEPConstants.APPLICATION_JSON_CHARSET);
            WebTransportUtils.setHttpRequestBody(httpConnection, payload);
            byte[] responseBytes = WebTransportUtils.getResponseBytes(httpConnection);
            WebTransportUtils.handleSpecificWebException(responseBytes, httpConnection);
            return gson.fromJson(new String(responseBytes), DevicesWithSyncInfo.class);
        } finally {
            if (httpConnection != null) {
                httpConnection.disconnect();
            }
        }
    }

    /**
     * Returns information about the given serial number's device.
     * @param device bean containing an array of strings containing device serial numbers.
     * @return DeviceDetailsFilter bean which contains device details.
     * @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.
     */
    public static DeviceDetailsFilter getDevice(DeviceSerials device)
            throws AppleDEPConnectorException, DEPServerTokenException, DeviceManagementException {
        HttpURLConnection httpConnection = null;
        try {
            Gson gson = new Gson();
            String payload = gson.toJson(device);
            httpConnection = WebTransportUtils.createHttpConnection(DEPConstants.DEVICE_DETAILS_URL,
                                    DEPConstants.HTTP_POST, DEPConstants.APPLICATION_JSON_CHARSET);
            WebTransportUtils.setHttpRequestBody(httpConnection, payload);
            byte[] responseBytes = WebTransportUtils.getResponseBytes(httpConnection);
            WebTransportUtils.handleSpecificWebException(responseBytes, httpConnection);
            String[] serialNumber = device.getDevices();
            String deviceDetails = (new JSONObject(new String(responseBytes)).
                    getJSONObject("devices").getJSONObject(serialNumber[0])).toString();
            DeviceDetailsFilter deviceDetailsResponse = gson.fromJson(deviceDetails,
                                                                      DeviceDetailsFilter.class);
            deviceDetailsResponse.setSerialNumber(serialNumber[0]);
            return deviceDetailsResponse;
        } finally {
            if (httpConnection != null) {
                httpConnection.disconnect();
            }
        }
    }

    /**
     * Notifies Apple’s servers that the organization no longer owns one or more deviceSerials.
     * Disowning a device is a permanent action. After a short grace period, a disowned device
     * cannot be reassigned to an MDM server in the organization.
     * @param deviceSerials bean containing an array of strings with device serial numbers.
     * @return DevicesWithProfileAssignmentStatus bean which contains details of disowned
     * deviceSerials with status.
     * @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.
     */
    public static DevicesWithProfileAssignmentStatus disownDevices(DeviceSerials deviceSerials)
            throws AppleDEPConnectorException, DEPServerTokenException, DeviceManagementException {
        HttpURLConnection httpConnection = null;
        try {
            Gson gson = new Gson();
            String payload = gson.toJson(deviceSerials);
            httpConnection = WebTransportUtils.createHttpConnection(DEPConstants.DISOWN_DEVICES_URL,
                                    DEPConstants.HTTP_POST, DEPConstants.APPLICATION_JSON_CHARSET);
            WebTransportUtils.setHttpRequestBody(httpConnection, payload);
            byte[] responseBytes = WebTransportUtils.getResponseBytes(httpConnection);
            WebTransportUtils.handleSpecificWebException(responseBytes, httpConnection);
            return EndpointUtils.createProfileDisownResponseBean(new String(responseBytes));
        } finally {
            if (httpConnection != null) {
                httpConnection.disconnect();
            }
        }
    }

    /**
     * Create an Apple DEP profile with the given details.
     * @param deviceProfile bean containing information needed to define profile.
     * @return ProfileAssignmentStatuses bean which contains defined profile details.
     * @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.
     */
    public static ProfileAssignmentStatuses createProfile(DeviceProfile deviceProfile)
            throws AppleDEPConnectorException, DEPServerTokenException, DeviceManagementException {
        HttpURLConnection httpConnection = null;
        try {
            Gson gson = new Gson();
            String payload = gson.toJson(deviceProfile);
            httpConnection = WebTransportUtils.createHttpConnection(DEPConstants.DEFINE_PROFILE_URL,
                                    DEPConstants.HTTP_POST, DEPConstants.APPLICATION_JSON_CHARSET);
            WebTransportUtils.setHttpRequestBody(httpConnection, payload);
            byte[] responseBytes = WebTransportUtils.getResponseBytes(httpConnection);
            WebTransportUtils.handleSpecificWebException(responseBytes, httpConnection);
            return EndpointUtils.createProfileResponseBean(new String(responseBytes));
        } finally {
            if (httpConnection != null) {
                httpConnection.disconnect();
            }
        }
    }

    /**
     * Assign Apple DEP profile to the specified deviceSerials.
     * @param profileWithDevices bean containing an array of strings with device serial numbers
     *                           and a unique profile identifier.
     * @return ProfileAssignmentStatuses bean which contains assigned profile details.
     * @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.
     */
    public static ProfileAssignmentStatuses assignProfile(ProfileWithDevices profileWithDevices)
            throws AppleDEPConnectorException, DEPServerTokenException, DeviceManagementException {
        HttpURLConnection httpConnection = null;
        try {
            Gson gson = new Gson();
            String payload = gson.toJson(profileWithDevices);
            httpConnection = WebTransportUtils.createHttpConnection(DEPConstants.ASSIGN_PROFILE_URL,
                                    DEPConstants.HTTP_PUT, DEPConstants.APPLICATION_JSON_CHARSET);
            WebTransportUtils.setHttpRequestBody(httpConnection, payload);
            byte[] responseBytes = WebTransportUtils.getResponseBytes(httpConnection);
            WebTransportUtils.handleSpecificWebException(responseBytes, httpConnection);
            return EndpointUtils.createProfileResponseBean(new String(responseBytes));
        } finally {
            if (httpConnection != null) {
                httpConnection.disconnect();
            }
        }
    }

    /**
     * Returns information about a profile.
     * @param profileID string containing unique profile identifier.
     * @return Profile bean which contains fetched profile details.
     * @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.
     */
    public static Profile getProfile(String profileID) throws AppleDEPConnectorException,
                                                              DEPServerTokenException,
                                                              DeviceManagementException {
        String UUID = "?profile_uuid=" + profileID;
        String requestURL = DEPConstants.FETCH_PROFILE_URL + UUID;
        HttpURLConnection httpConnection = null;
        try {
            httpConnection = WebTransportUtils.createHttpConnection(requestURL,
                                                                    DEPConstants.HTTP_GET, null);
            byte[] responseBytes = WebTransportUtils.getResponseBytes(httpConnection);
            WebTransportUtils.handleSpecificWebException(responseBytes, httpConnection);
            Gson gson = new Gson();
            return gson.fromJson(new String(responseBytes), Profile.class);
        } finally {
            if (httpConnection != null) {
                httpConnection.disconnect();
            }
        }
    }

    /**
     * Removes profile from the specified deviceSerials.
     * After this call, the deviceSerials in the list will have no profiles associated with them.
     * However, if those deviceSerials have already obtained the profile, this has no effect until the
     * device is wiped and activated again.
     * @param deviceSerials payload containing an array of strings with device serial numbers.
     * @return DevicesWithProfileAssignmentStatus bean which contains details of the removed profile.
     * @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.
     */
    public static DevicesWithProfileAssignmentStatus removeProfile(DeviceSerials deviceSerials)
            throws AppleDEPConnectorException, DEPServerTokenException, DeviceManagementException {
        HttpURLConnection httpConnection = null;
        try {
            Gson gson = new Gson();
            String payload = gson.toJson(deviceSerials);
            httpConnection = WebTransportUtils.createHttpConnection(DEPConstants.REMOVE_PROFILE_URL,
                                DEPConstants.HTTP_DELETE, DEPConstants.APPLICATION_JSON_CHARSET);
            WebTransportUtils.setHttpRequestBody(httpConnection, payload);
            byte[] responseBytes = WebTransportUtils.getResponseBytes(httpConnection);
            WebTransportUtils.handleSpecificWebException(responseBytes, httpConnection);
            return EndpointUtils.createProfileDisownResponseBean(new String(responseBytes));
        } finally {
            if (httpConnection != null) {
                httpConnection.disconnect();
            }
        }
    }

    /**
     * Checks the expiration of the server token. (validity of the token is 1 year from the
     * issued date)
     * @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.
     */
    public static void checkTokenExpiration() throws DEPServerTokenException,
                                                     AppleDEPConnectorException,
                                                     DeviceManagementException {
        ServerToken token = TokenUtils.retrieveDEPConfiguration();
        if(token == null || token.getAccessToken() == null) {
            throw new DEPServerTokenException("DEP token is invalid. Please configure the tokens " +
                    "in tenant configurations.");
        }
        Date currentDate = new Date();
        Date tokenExpirationDate = DatatypeConverter.parseDateTime(token.getAccessTokenExpiry()).
                                                                                        getTime();
        if (tokenExpirationDate.compareTo(currentDate) != 1) {
            throw new DEPServerTokenException("The .p7m Server Token has been expired.");
        }
    }

    /**
     * Creates profile response bean by traversing through the JSON object.
     * @param profileDetails string containing the response from Apple DEP portal.
     * @return ProfileAssignmentStatuses bean.
     * @throws AppleDEPConnectorException if the method encounters any Apple DEP related issue.
     */
    public static ProfileAssignmentStatuses createProfileResponseBean(String profileDetails)
            throws AppleDEPConnectorException {
        JSONObject appleProfileDetails = new JSONObject(profileDetails);
        String profileID = appleProfileDetails.getString("profile_uuid");
        List<ProfileAssignmentStatus> profileAssignmentStatusList = EndpointUtils.
                createProfileDeviceDetailsList(profileDetails);
        ProfileAssignmentStatuses profileAssignmentStatuses = new ProfileAssignmentStatuses();
        profileAssignmentStatuses.setProfileUuid(profileID);
        profileAssignmentStatuses.setDevices(profileAssignmentStatusList);
        return profileAssignmentStatuses;
    }

    /**
     * Create profile disown response bean by traversing through the JSON object.
     * @param deviceSerials string containing the response from Apple DEP portal.
     * @return DevicesWithProfileAssignmentStatus bean.
     * @throws AppleDEPConnectorException if the method encounters any Apple DEP related issue.
     */
    public static DevicesWithProfileAssignmentStatus createProfileDisownResponseBean(String deviceSerials)
            throws AppleDEPConnectorException {
        List<ProfileAssignmentStatus> profileDetails = EndpointUtils.
                createProfileDeviceDetailsList(deviceSerials);
        DevicesWithProfileAssignmentStatus profileResponse = new DevicesWithProfileAssignmentStatus();
        profileResponse.setDevices(profileDetails);
        return profileResponse;
    }

    /**
     * Creates a list containing profile device details list.
     * @param devices string containing the details of the deviceSerials.
     * @return List<ProfileAssignmentStatus> containing profile device details.
     * @throws AppleDEPConnectorException if the method encounters any Apple DEP related issue.
     */
    public static List<ProfileAssignmentStatus> createProfileDeviceDetailsList(String devices)
            throws AppleDEPConnectorException {
        JSONObject appleProfileDetails = new JSONObject(devices);
        JSONObject deviceDetails = appleProfileDetails.getJSONObject("devices");
        List<ProfileAssignmentStatus> profileAssignmentStatusList = new ArrayList<>();
        if (deviceDetails != null) {
            for (int i=0; i<deviceDetails.length(); i++) {
                ProfileAssignmentStatus profileAssignmentStatus = new ProfileAssignmentStatus();
                String serialNumber = deviceDetails.names().getString(i);
                String profileStatus = deviceDetails.get(serialNumber).toString();
                profileAssignmentStatus.setSerialNumber(serialNumber);
                profileAssignmentStatus.setProfileAssignmentStatus(profileStatus);
                profileAssignmentStatusList.add(profileAssignmentStatus);
            }
        }
        return profileAssignmentStatusList;
    }
}
