package org.wso2.carbon.device.mgt.ios.util;

import com.google.gson.Gson;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.apimgt.application.extension.dto.ApiApplicationKey;
import org.wso2.carbon.apimgt.application.extension.exception.APIManagerException;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
import org.wso2.carbon.device.mgt.ios.api.utils.util.OperationMapping;
import org.wso2.carbon.device.mgt.ios.apns.exception.APNSException;
import org.wso2.carbon.device.mgt.ios.apns.service.PushNotificationService;
import org.wso2.carbon.device.mgt.ios.core.publisher.TokenPersistence;
import org.wso2.carbon.device.mgt.ios.core.util.CertificateConfig;
import org.wso2.carbon.device.mgt.ios.dep.utils.TokenUtils;
import org.wso2.carbon.device.mgt.ios.payload.beans.*;
import org.wso2.carbon.device.mgt.ios.payload.dto.PayloadOperationWrapper;
import org.wso2.carbon.device.mgt.ios.payload.exception.PListException;
import org.wso2.carbon.device.mgt.ios.payload.exception.ProfileConfigurationException;
import org.wso2.carbon.device.mgt.ios.payload.operation.AirOperations;
import org.wso2.carbon.device.mgt.ios.payload.operation.ApplicationOperations;
import org.wso2.carbon.device.mgt.ios.payload.operation.CalendarOperations;
import org.wso2.carbon.device.mgt.ios.payload.operation.CellularOperations;
import org.wso2.carbon.device.mgt.ios.payload.operation.DeviceOperations;
import org.wso2.carbon.device.mgt.ios.payload.operation.DomainOperations;
import org.wso2.carbon.device.mgt.ios.payload.operation.EmailOperations;
import org.wso2.carbon.device.mgt.ios.payload.operation.LDAPOperations;
import org.wso2.carbon.device.mgt.ios.payload.operation.PasscodeOperations;
import org.wso2.carbon.device.mgt.ios.payload.operation.RestrictionOperations;
import org.wso2.carbon.device.mgt.ios.payload.operation.VPNOperations;
import org.wso2.carbon.device.mgt.ios.payload.operation.WIFIOperations;
import org.wso2.carbon.device.mgt.ios.payload.operation.WebClipOperations;
import org.wso2.carbon.device.mgt.mobile.impl.ios.dto.DEPDevice;
import org.wso2.carbon.identity.jwt.client.extension.dto.AccessTokenInfo;
import org.wso2.carbon.policy.mgt.core.util.PolicyManagementConstants;
import org.wso2.carbon.device.mgt.mobile.impl.ios.profile.IOSProfileService;
import org.wso2.carbon.user.api.UserStoreException;

public class OperationMapper {

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

    public PayloadOperationWrapper mapToProfileOperation(Device device, OperationMapping operationMapping, String
            payload, String uuid) throws PListException, ProfileConfigurationException {

        Gson gson = new Gson();

        switch (operationMapping) {

            case CELLULAR: {
                Cellular cellular = gson.fromJson(payload, Cellular.class);
                cellular.setUUID(uuid);

                CellularOperations cellularOperations = new CellularOperations();
                return cellularOperations.createCellularPayload(cellular);
            }
            case APN: {
                APNConfig apnConfig = gson.fromJson(payload, APNConfig.class);
                apnConfig.setUUID(uuid);

                CellularOperations cellularOperations = new CellularOperations();
                return cellularOperations.createAPNPayload(apnConfig);
            }
            case WIFI: {
                WIFI wifi = gson.fromJson((String)payload, WIFI.class);
                wifi.setUUID(uuid);

                WIFIOperations wifiOperations = new WIFIOperations();
                return wifiOperations.createWIFIPayload(wifi);
            }
            case EMAIL: {
                Email email = gson.fromJson(payload, Email.class);
                email.setUUID(uuid);

                EmailOperations emailOperations = new EmailOperations();
                return emailOperations.createEmailPayload(email);
            }
            case AIR_PLAY: {
                AirPlay airPlay = gson.fromJson(payload, AirPlay.class);
                airPlay.setUUID(uuid);

                AirOperations airOperations = new AirOperations();
                return airOperations.createAirPlayPayload(airPlay);
            }
            case RESTRICTION: {
                Restriction restriction = gson.fromJson(payload, Restriction.class);
                restriction.setUUID(uuid);

                RestrictionOperations restrictionOperations = new RestrictionOperations();
                return restrictionOperations.createRestrictionPayload(restriction);
            }
            case LDAP: {
                LDAP ldap = gson.fromJson(payload, LDAP.class);
                ldap.setUUID(uuid);

                LDAPOperations ldapOperations = new LDAPOperations();
                return ldapOperations.createLDAPPayload(ldap);
            }
            case CALDAV: {
                CalDAV calDAV = gson.fromJson(payload, CalDAV.class);
                calDAV.setUUID(uuid);

                CalendarOperations calendarOperations = new CalendarOperations();
                return calendarOperations.createCalDAVPayload(calDAV);
            }
            case CALENDAR_SUBSCRIPTION: {
                CalendarSubscription calendarSubscription = gson.fromJson(payload,
                        CalendarSubscription.class);
                calendarSubscription.setUUID(uuid);

                CalendarOperations calendarOperations = new CalendarOperations();
                return calendarOperations.createCalenderSubscriptionPayload(calendarSubscription);
            }
            case PASSCODE_POLICY: {
                PasscodePolicy passcodePolicy = gson.fromJson(payload,
                        PasscodePolicy.class);
                passcodePolicy.setUUID(uuid);

                PasscodeOperations passcodeOperations = new PasscodeOperations();
                return passcodeOperations.createPasscodePolicyPayload(passcodePolicy);
            }
            case WEB_CLIP: {
                WebClip webClip = gson.fromJson(payload, WebClip.class);
                webClip.setUUID(uuid);
                DeviceProfile deviceProfile = new DeviceProfile();
                deviceProfile.setOperationId(uuid);
                deviceProfile.setProfileId(uuid);
                deviceProfile.setOperationCode(operationMapping.getCode());
                deviceProfile.setDeviceId(device.getDeviceIdentifier());
                deviceProfile.setProperties(webClip.getURL());
                IOSProfileService profileService = IOSServiceUtils.getProfileService();
                profileService.saveProfile(deviceProfile);

                WebClipOperations webClipOperations = new WebClipOperations();
                return webClipOperations.createWebClipPayload(webClip);
            }
            case VPN: {
                VPN vpn = gson.fromJson(payload, VPN.class);
                vpn.setUUID(uuid);

                VPNOperations vpnOperations = new VPNOperations();
                return vpnOperations.createVPNPayload(vpn);
            }
            case PER_APP_VPN: {
                PerAppVPN perAppVPN = gson.fromJson(payload, PerAppVPN.class);
                perAppVPN.setUUID(uuid);

                VPNOperations vpnOperations = new VPNOperations();
                return vpnOperations.createPerAppVPNPayload(perAppVPN);
            }
            case APP_TO_PER_APP_VPN_MAPPING: {
                AppToPerAppVPNMapping appToPerAppVPNMapping = gson.fromJson(payload,
                        AppToPerAppVPNMapping.class);
                appToPerAppVPNMapping.setUUID(uuid);

                VPNOperations vpnOperations = new VPNOperations();
                return vpnOperations.createAppToPerAppVPNMappingPayload(appToPerAppVPNMapping);
            }
            case APP_LOCK: {
                AppLock appLock = gson.fromJson(payload, AppLock.class);
                appLock.setUUID(uuid);

                ApplicationOperations applicationOperations = new ApplicationOperations();
                return applicationOperations.createAppLockPayload(appLock);
            }
            case DOMAIN: {
                Domain domain = gson.fromJson(payload, Domain.class);
                domain.setUUID(uuid);

                DomainOperations domainOperations = new DomainOperations();
                return domainOperations.createDomainPayload(domain);
            }
        }

        return null;
    }

    public String mapToCommandOperations(Device device, OperationMapping operationMapping, Operation operation)
            throws PListException, ProfileConfigurationException, APNSException {

        Gson gson = new Gson();
        String operationID = Integer.valueOf(operation.getId()).toString();

        switch (operationMapping) {
            case DEVICE_LOCK: {
                GenericDeviceOperation genericDeviceOperation = new GenericDeviceOperation();
                genericDeviceOperation.setUUID(operationID);

                DeviceOperations deviceOperations = new DeviceOperations();
                return deviceOperations.createDeviceLockPayload(genericDeviceOperation);
            }
            case APPLICATION_LIST: {
                GenericDeviceOperation genericDeviceOperation = new GenericDeviceOperation();
                genericDeviceOperation.setUUID(operationID);

                DeviceOperations deviceOperations = new DeviceOperations();
                return deviceOperations.createApplicationListPayload(genericDeviceOperation);
            }
            case PROFILE_LIST: {
                GenericDeviceOperation genericDeviceOperation = new GenericDeviceOperation();
                genericDeviceOperation.setUUID(operationID);

                DeviceOperations deviceOperations = new DeviceOperations();
                return deviceOperations.createProfileListPayload(genericDeviceOperation);
            }
            case POLICY_REVOKE: {
                RemoveProfile removeProfile = new RemoveProfile();
                //Setting the POLICY_BUNDLE as payload-identifier that should be removed.
                removeProfile.setIdentifier(PolicyManagementConstants.POLICY_BUNDLE);
                removeProfile.setUUID(operationID);

                DeviceOperations deviceOperations = new DeviceOperations();
                return deviceOperations.createRemoveProfilePayload(removeProfile);
            }
            case CLEAR_PASSCODE: {
                GenericDeviceOperation genericDeviceOperation = new GenericDeviceOperation();
                genericDeviceOperation.setUUID(operationID);

                DeviceOperations deviceOperations = new DeviceOperations();
                TokenPersistence tokenPersistence = new TokenPersistence();
                String unlockToken = tokenPersistence.getPropertyValue(device, TokenPersistence.UNLOCK_TOKEN);
                return deviceOperations.createClearPasscodePayload(genericDeviceOperation, unlockToken);
            }
            case DEVICE_INFO: {
                GenericDeviceOperation genericDeviceOperation = new GenericDeviceOperation();
                genericDeviceOperation.setUUID(operationID);

                DeviceOperations deviceOperations = new DeviceOperations();
                return deviceOperations.createDeviceInformationPayload(genericDeviceOperation);
            }
            case GET_RESTRICTIONS: {
                InstalledRestrictions installedRestrictions = gson.fromJson((String) operation.getPayLoad(),
                        InstalledRestrictions.class);
                installedRestrictions.setUUID(operationID);

                DeviceOperations deviceOperations = new DeviceOperations();
                return deviceOperations.createRequestRestrictionPayload(installedRestrictions);
            }
            case INSTALL_STORE_APPLICATION: {
                AppStoreApplication appStoreApplication = gson.fromJson((String) operation.getPayLoad(),
                        AppStoreApplication.class);
                appStoreApplication.setUUID(operationID);

                ApplicationOperations applicationOperations = new ApplicationOperations();
                return applicationOperations.createAppStoreApplicationPayload(appStoreApplication);
            }
            case INSTALL_ENTERPRISE_APPLICATION: {
                EnterpriseApplication enterpriseApplication = gson.fromJson((String) operation.getPayLoad(),
                        EnterpriseApplication.class);
                enterpriseApplication.setUUID(operationID);
                TokenDetails tokenDetails = null;
                try {
                    String agentAppId = TokenUtils.getAgentAppId();
                    DEPDevice depDevice = IOSServiceUtils.getProfileService()
                            .getDEPDeviceByDeviceId(device.getDeviceIdentifier());
                    if (depDevice != null && StringUtils.isNotEmpty(depDevice.getDeviceIdentifier())
                            && StringUtils.isNotEmpty(agentAppId) && depDevice.getAgentRequired()) {
                        ApiApplicationKey apiApplicationKey = OAuthUtils.
                                getClientCredentials(depDevice.getTenantDomain());
                        AccessTokenInfo token = OAuthUtils.getOAuthCredentials(
                                apiApplicationKey, depDevice.getUsername() + "@" +
                                        depDevice.getTenantDomain(), false);
                        tokenDetails = new TokenDetails();
                        tokenDetails.setAccessToken(token.getAccessToken());
                        tokenDetails.setRefreshToken(token.getRefreshToken());
                        tokenDetails.setClientId(apiApplicationKey.getConsumerKey());
                        tokenDetails.setClientSecret(apiApplicationKey.getConsumerSecret());
                        tokenDetails.setDeviceId(device.getDeviceIdentifier());
                    }
                } catch (APIManagerException e) {
                    String errorMsg = "Error while installing the agent application for device " +
                            "with id: " + device.getDeviceIdentifier();
                    log.error(errorMsg, e);
                    throw new PListException(errorMsg, e);
                } catch (UserStoreException e) {
                    String errorMsg = "Error while generating app install payload for device id: " +
                            device.getDeviceIdentifier();
                    log.error(errorMsg, e);
                    throw new PListException(errorMsg, e);
                } catch (DeviceManagementException e) {
                    String errorMsg = "Error while getting the agent app ID from tenant configs.";
                    log.error(errorMsg, e);
                    throw new PListException(errorMsg, e);
                }
                ApplicationOperations applicationOperations = new ApplicationOperations();
                return applicationOperations.createEnterpriseApplicationPayload(enterpriseApplication, tokenDetails);
            }
            case REMOVE_APPLICATION: {
                RemoveApplication removeApplication = gson.fromJson((String) operation.getPayLoad(),
                        RemoveApplication.class);
                if(removeApplication.getBundleId() == null) {
                    IOSProfileService profileService = IOSServiceUtils.getProfileService();
                    DeviceProfile deviceProfile = profileService.getProfile(device.getDeviceIdentifier(),
                            removeApplication.getUrl());
                    RemoveProfile removeProfile = new RemoveProfile();
                    removeProfile.setIdentifier(deviceProfile.getProfileId());
                    removeProfile.setUUID(operationID);

                    DeviceOperations deviceOperations = new DeviceOperations();
                    return deviceOperations.createRemoveProfilePayload(removeProfile);
                } else {
                    removeApplication.setUUID(operationID);
                    ApplicationOperations applicationOperations = new ApplicationOperations();
                    return applicationOperations.createRemoveApplicationPayload(removeApplication);
                }
            }
            case RING: {
                if (isAgentInstalled(device)) {
                    initializeSilentPushService(device, operation);
                    return "";
                } else {
                    throw new APNSException("ios agent is not enrolled for this device. Agent is required to perform "
                            + "the ring operation on the device");
                }
            }
            case DEVICE_LOCATION: {
                if (isAgentInstalled(device)) {
                    initializeSilentPushService(device, operation);
                    return "";
                } else {
                    throw new APNSException("ios agent is not enrolled for this device. Agent is required to perform "
                            + "the device location operation on the device");
                }
            }
            case NOTIFICATION: {
                if (isAgentInstalled(device)) {
                    Notification notification = gson.fromJson((String) operation.getPayLoad(), Notification.class);
                    initializeNormalPushService(device, notification);
                    return "";
                } else {
                    throw new APNSException("ios agent is not enrolled for this device. Agent is required to perform "
                            + "the notification operation on the device");
                }
            }
            case WIPE_DATA: {
                GenericDeviceOperation genericDeviceOperation = new GenericDeviceOperation();
                genericDeviceOperation.setUUID(operationID);

                DeviceOperations deviceOperations = new DeviceOperations();
                return deviceOperations.createDataWipe(genericDeviceOperation);
            }
        }

        return null;
    }

    public void initializeSilentPushService(Device device, Operation operation) throws APNSException {
        PushNotificationService pushNotificationService = IOSServiceUtils.getPushNotificationService();

        //TODO fetch this from tenant configurations
        TokenPersistence tokenPersistence = new TokenPersistence();
        //TODO for now just fetching this token from unlock token. Change it.
        try {
            pushNotificationService
                    .sendSilentNotification(CertificateConfig.getAPNSPem(), CertificateConfig.getAPNSPassword(),
                                            CertificateConfig.isProductionAPNS(),
                                            tokenPersistence.getPropertyValue(device.getProperties(),
                                                                              TokenPersistence.APNS_PUSH_TOKEN),
                                            operation.getCode() , operation.getId()
                    );
        }  catch (DeviceManagementException e) {
            String errorMsg = "Error occurred while fetching tenant " +
                              "configurations";
            log.error(errorMsg, e);
            throw new APNSException(errorMsg, e);
        }
    }

    public void initializeNormalPushService(Device device, Notification notification) throws APNSException {
        PushNotificationService pushNotificationService = IOSServiceUtils.getPushNotificationService();

        //TODO fetch this from tenant configurations
        TokenPersistence tokenPersistence = new TokenPersistence();
        try {
            //TODO for now just fetching this token from unlock token. Change it.
            pushNotificationService
                    .sendNotification(CertificateConfig.getAPNSPem(), CertificateConfig.getAPNSPassword(),
                                      CertificateConfig.isProductionAPNS(),
                                      tokenPersistence.getPropertyValue(device.getProperties(),
                                                                        TokenPersistence.APNS_PUSH_TOKEN),
                            notification.getMessageTitle(), notification.getMessageText());
        } catch (DeviceManagementException e) {
            String errorMsg = "Error occurred while fetching tenant " +
                              "configurations";
            log.error(errorMsg, e);
            throw new APNSException(errorMsg, e);
        }
    }

    /**
     * This method checks whether the ios agent is installed and enrolled in the particular device.
     *
     * @param device Device which we need to check whether agent is installed or not.
     * @return true if the agent is installed, otherwise false.
     */
    private boolean isAgentInstalled(Device device) {
        return  TokenPersistence.getPropertyValue(device.getProperties(), TokenPersistence.APNS_PUSH_TOKEN) != null;

    }
}
