package com.braintree.b2bfacades;

import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.exceptions.PaypalAccessTokenNotFoundException;
import com.braintree.facade.BrainTreeRegistrationUserFacade;
import com.braintree.facade.BraintreeB2BRegistrationUserFacade;
import com.braintree.hybris.data.BrainTreeConnectWithPayPalUserData;
import com.braintree.hybris.data.BrainTreeConnectWithPayPalUserExistB2BData;
import com.braintree.hybris.data.BraintreeConnectWithPayPalConnectB2BRegisterData;
import com.braintree.model.PaypalAccessTokenModel;
import com.braintree.service.BrainTreeAccessTokenService;
import com.braintree.service.BrainTreeConnectWithPayPalService;
import de.hybris.platform.b2b.model.B2BCustomerModel;
import de.hybris.platform.cms2.servicelayer.services.CMSSiteService;
import de.hybris.platform.commerceservices.customer.DuplicateUidException;
import de.hybris.platform.core.model.c2l.CountryModel;
import de.hybris.platform.core.model.user.CustomerModel;
import de.hybris.platform.core.model.user.TitleModel;
import de.hybris.platform.core.model.user.UserModel;
import de.hybris.platform.b2b.model.B2BRegistrationModel;
import de.hybris.platform.servicelayer.exceptions.UnknownIdentifierException;
import de.hybris.platform.servicelayer.i18n.CommonI18NService;
import de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.servicelayer.user.UserService;
import de.hybris.platform.store.services.BaseStoreService;
import de.hybris.platform.tx.Transaction;
import de.hybris.platform.workflow.WorkflowProcessingService;
import de.hybris.platform.workflow.WorkflowService;
import de.hybris.platform.workflow.WorkflowTemplateService;
import de.hybris.platform.workflow.model.WorkflowModel;
import de.hybris.platform.workflow.model.WorkflowTemplateModel;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.BeanUtils;

public class DefaultBraintreeB2BRegistrationUserFacade implements BraintreeB2BRegistrationUserFacade {

    private static final Logger LOG = Logger.getLogger(DefaultBraintreeB2BRegistrationUserFacade.class);

    private static final String REGISTRATION_WORKFLOW = "B2BUserRegistration";
    private static final String USER_ALREADY_EXIST_MESSAGE = "User with uid '%s' already exists!";
    private static final String STARTING_WORKFLOW_MESSAGE = "Starting workflow for customer with email '%s' and organization (company name) '%s'.";

    private BrainTreeConnectWithPayPalService brainTreeConnectWithPayPalService;
    private ModelService modelService;
    private UserService userService;
    private CommonI18NService commonI18NService;
    private WorkflowTemplateService workflowTemplateService;
    private WorkflowService workflowService;
    private WorkflowProcessingService workflowProcessingService;
    private BaseStoreService baseStoreService;
    private CMSSiteService cmsSiteService;
    private BrainTreeRegistrationUserFacade registrationUserFacade;
    private BrainTreeConfigService brainTreeConfigService;
    private BrainTreeAccessTokenService braintreeAccessTokenService;

    @Override
    public String registerB2BPayPalUser(BraintreeConnectWithPayPalConnectB2BRegisterData registerData)
            throws DuplicateUidException {
        String accessToken = braintreeAccessTokenService.getPaypalAccessToken(registerData.getAccessTokenGuid())
                .map(PaypalAccessTokenModel::getAccessToken)
                .orElseThrow(PaypalAccessTokenNotFoundException::new);

        final BrainTreeConnectWithPayPalUserData userData = brainTreeConnectWithPayPalService.getUserDataByAccessToken(accessToken);
        registerData.setEmail(registrationUserFacade.getEmailFromPayPalUserData(userData));
        registerData.setName(userData.getName());

        final Transaction tx = getCurrentTransaction();
        tx.begin();
        boolean success = false;
        String payerId = StringUtils.EMPTY;
        try {
            final boolean userExists = userService.isUserExisting(registerData.getEmail());
            if (userExists) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug(String.format(USER_ALREADY_EXIST_MESSAGE, registerData.getEmail()));
                }
                throw new DuplicateUidException(
                        String.format(USER_ALREADY_EXIST_MESSAGE, registerData.getEmail()));
            }

            final B2BRegistrationModel registration = toRegistrationModel(registerData);
            modelService.save(registration);

            final CustomerModel customer = toCustomerModel(registerData);
            modelService.save(customer);

            final WorkflowTemplateModel workflowTemplate = workflowTemplateService.getWorkflowTemplateForCode(REGISTRATION_WORKFLOW);

            final WorkflowModel workflow = workflowService.createWorkflow(workflowTemplate, registration,
                    userService.getAdminUser());
            modelService.save(workflow);

            if (!brainTreeConfigService.isPayPalConnectAddPaymentMethodFlow()) {
                braintreeAccessTokenService.removePaypalAccessToken(registerData.getAccessTokenGuid());
            }

            LOG.info(String.format(STARTING_WORKFLOW_MESSAGE, registration.getEmail(), registration.getCompanyName()));

            workflowProcessingService.startWorkflow(workflow);
            tx.commit();
            success = true;
        } catch (Exception e) {
            final String message = "Can not register B2B PayPal user " + e.getMessage();
            LOG.error(message, e);
        } finally {
            if (!success) {
                tx.rollback();
            }
        }

        if (success) {
            registrationUserFacade
                    .setPayerIdToUser(registrationUserFacade.getEmailFromPayPalUserData(userData), userData.getPayerId());
            payerId = userData.getPayerId();
        }
        return payerId;
    }

    @Override
    public BrainTreeConnectWithPayPalUserExistB2BData isCustomerExistAndApprovedByUID(String email) {
        final BrainTreeConnectWithPayPalUserExistB2BData userExistB2BData = new BrainTreeConnectWithPayPalUserExistB2BData();
        userExistB2BData.setExist(false);
        userExistB2BData.setApproved(false);
        try {
            final UserModel userModel = userService.getUserForUID(email);
            userExistB2BData.setExist(true);
            userExistB2BData.setApproved(userModel.getItemModelContext().getItemType().equals(B2BCustomerModel._TYPECODE));
        } catch (UnknownIdentifierException e) {
            final String message = "Cannot find user with uid '" + email + "'";
            LOG.error(message, e);
        }
        return userExistB2BData;
    }

    private B2BRegistrationModel toRegistrationModel(final BraintreeConnectWithPayPalConnectB2BRegisterData data) {
        final B2BRegistrationModel model = modelService.create(B2BRegistrationModel.class);
        BeanUtils.copyProperties(data, model, "titleCode", "companyAddressCountryIso", "companyAddressRegion",
                "baseStore", "cmsSite", "currency",
                "language");

        if (StringUtils.isNotBlank(data.getTitleCode())) {
            final TitleModel title = userService.getTitleForCode(data.getTitleCode());
            model.setTitle(title);
        }

        final CountryModel country = commonI18NService.getCountry(data.getCompanyAddressCountryIso());
        model.setCompanyAddressCountry(country);
        model.setBaseStore(baseStoreService.getCurrentBaseStore());
        model.setBaseSite(cmsSiteService.getCurrentSite());
        model.setCurrency(commonI18NService.getCurrentCurrency());
        model.setLanguage(commonI18NService.getCurrentLanguage());
        return model;
    }

    private CustomerModel toCustomerModel(BraintreeConnectWithPayPalConnectB2BRegisterData registerData) {
        final CustomerModel model = modelService.create(CustomerModel.class);

        model.setName(WordUtils.capitalizeFully(registerData.getName()));
        model.setUid(registerData.getEmail());
        model.setLoginDisabled(true);
        model.setSessionLanguage(commonI18NService.getCurrentLanguage());
        model.setSessionCurrency(commonI18NService.getCurrentCurrency());
        model.setPasswordForSingleAuthenticationActive(false);

        if (StringUtils.isNotBlank(registerData.getTitleCode())) {
            final TitleModel title = userService.getTitleForCode(registerData.getTitleCode());
            model.setTitle(title);
        }

        return model;
    }

    protected Transaction getCurrentTransaction() {
        return Transaction.current();
    }

    public void setBrainTreeConnectWithPayPalService(
            BrainTreeConnectWithPayPalService brainTreeConnectWithPayPalService) {
        this.brainTreeConnectWithPayPalService = brainTreeConnectWithPayPalService;
    }

    public void setModelService(ModelService modelService) {
        this.modelService = modelService;
    }

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void setCommonI18NService(CommonI18NService commonI18NService) {
        this.commonI18NService = commonI18NService;
    }

    public void setWorkflowTemplateService(WorkflowTemplateService workflowTemplateService) {
        this.workflowTemplateService = workflowTemplateService;
    }

    public void setWorkflowService(WorkflowService workflowService) {
        this.workflowService = workflowService;
    }

    public void setWorkflowProcessingService(WorkflowProcessingService workflowProcessingService) {
        this.workflowProcessingService = workflowProcessingService;
    }

    public void setBaseStoreService(BaseStoreService baseStoreService) {
        this.baseStoreService = baseStoreService;
    }


    public void setCmsSiteService(CMSSiteService cmsSiteService) {
        this.cmsSiteService = cmsSiteService;
    }

    public void setRegistrationUserFacade(BrainTreeRegistrationUserFacade registrationUserFacade) {
        this.registrationUserFacade = registrationUserFacade;
    }

    public void setBrainTreeConfigService(BrainTreeConfigService brainTreeConfigService) {
        this.brainTreeConfigService = brainTreeConfigService;
    }

    public void setBraintreeAccessTokenService(BrainTreeAccessTokenService braintreeAccessTokenService) {
        this.braintreeAccessTokenService = braintreeAccessTokenService;
    }
}
