package com.braintreeocc.controllers;

import com.braintree.exceptions.BraintreeAddressDuplicatedException;
import com.braintree.facade.BrainTreeConnectWithPayPalFacade;
import com.braintree.facade.BrainTreeSetUpPasswordFacade;
import com.braintree.facade.BrainTreeUserFacade;
import de.hybris.platform.commercefacades.customer.CustomerFacade;
import de.hybris.platform.commercefacades.user.data.AddressData;
import de.hybris.platform.commercefacades.user.data.CustomerData;
import de.hybris.platform.commercefacades.user.data.RegisterData;
import de.hybris.platform.commerceservices.customer.DuplicateUidException;
import de.hybris.platform.commerceservices.request.mapping.annotation.RequestMappingOverride;
import de.hybris.platform.commercewebservicescommons.dto.user.UserWsDTO;
import de.hybris.platform.webservicescommons.swagger.ApiBaseSiteIdParam;
import de.hybris.platform.webservicescommons.swagger.ApiFieldsParam;
import de.hybris.platform.commercewebservicescommons.dto.user.AddressWsDTO;
import de.hybris.platform.webservicescommons.util.YSanitizer;
import de.hybris.platform.commercewebservicescommons.dto.user.UserSignUpWsDTO;
import de.hybris.platform.webservicescommons.cache.CacheControl;
import de.hybris.platform.webservicescommons.cache.CacheControlDirective;
import de.hybris.platform.webservicescommons.swagger.ApiBaseSiteIdAndUserIdParam;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Validator;
import com.braintree.hybris.data.ws.BraintreeSetUpPasswordFormWsDTO;
import com.braintree.hybris.data.ws.BraintreeUnlinkConnectWithPayPalWsDTO;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.Locale;

@Controller
@RequestMapping(value = "/{baseSiteId}/users")
@CacheControl(directive = CacheControlDirective.PRIVATE)
@Api(tags = "BrainTree Users")
public class BraintreeUsersController extends BraintreeBaseCommerceController {

    private static final String ADDRESS_MAPPING = "firstName,lastName,titleCode,email,phone,cellphone,line1,line2,town,postalCode,region(isocode),district,country(isocode),defaultAddress";

    private static final String OBJECT_NAME_ADDRESS = "address";

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

    @Resource(name = "brainTreeSetUpPasswordFacade")
    private BrainTreeSetUpPasswordFacade braintreeSetUpPasswordFacade;
    
    @Resource(name = "brainTreeConnectWithPayPalFacade")
    private BrainTreeConnectWithPayPalFacade brainTreeConnectWithPayPalFacade;

    @Resource(name = "userSignUpDTOValidator")
    private Validator userSignUpDTOValidator;

    @Resource(name = "wsCustomerFacade")
    private CustomerFacade customerFacade;

    @Resource(name = "passwordStrengthValidator")
    private Validator passwordStrengthValidator;

    @Resource(name = "userFacade")
    private BrainTreeUserFacade brainTreeUserFacade;

    @Secured({ "ROLE_CUSTOMERGROUP", "ROLE_TRUSTED_CLIENT", "ROLE_CUSTOMERMANAGERGROUP" })
    @RequestMapping(value = "/{userId}/getSetUpPasswordFormData", method = RequestMethod.GET)
    @ResponseStatus(value = HttpStatus.OK)
    @ResponseBody
    @ApiOperation(value = "Get information about ability for setting single authentication password")
    @ApiBaseSiteIdAndUserIdParam
    public BraintreeSetUpPasswordFormWsDTO getSetUpPasswordFormData(
            @ApiParam(value = "User identifier.", required = true) @PathVariable final String userId) {
        BraintreeSetUpPasswordFormWsDTO setUpPasswordWsDTO = new BraintreeSetUpPasswordFormWsDTO();
        final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (containsRole(auth, "ROLE_TRUSTED_CLIENT") || containsRole(auth, "ROLE_CUSTOMERMANAGERGROUP")) {
            setUpPasswordWsDTO.setIsFormForSettingSingleAuthenticationPasswordActive(
                    braintreeSetUpPasswordFacade.isSetUpPasswordForm(userId));
        } else {
            setUpPasswordWsDTO.setIsFormForSettingSingleAuthenticationPasswordActive(
                    braintreeSetUpPasswordFacade.isSetUpPasswordForm());
        }
        return setUpPasswordWsDTO;
    }

    @Secured({"ROLE_CUSTOMERGROUP", "ROLE_TRUSTED_CLIENT", "ROLE_CUSTOMERMANAGERGROUP"})
    @RequestMapping(value = "/{userId}/setPassword", method = RequestMethod.POST)
    @ResponseStatus(value = HttpStatus.ACCEPTED)
    @ApiOperation(nickname = "setUpPassword", value = "Set up a password for Single Auth",
            notes = "Set up a password for Single Auth")
    @ApiBaseSiteIdAndUserIdParam
    public void setPasswordForSingleAuth(
            @ApiParam(value = "User identifier.", required = true) @PathVariable final String userId,
            @ApiParam(value = "New password.", required = true) @RequestParam final String newPassword) {
        final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        final UserSignUpWsDTO customer = new UserSignUpWsDTO();
        customer.setPassword(newPassword);
        validate(customer, "password", passwordStrengthValidator);
        if (containsRole(auth, "ROLE_TRUSTED_CLIENT") || containsRole(auth, "ROLE_CUSTOMERMANAGERGROUP")) {
            customerFacade.setPassword(userId, newPassword);
        } else {
            braintreeSetUpPasswordFacade.setUpPassword(newPassword);
        }
    }
    
    @Secured({ "ROLE_CUSTOMERGROUP", "ROLE_TRUSTED_CLIENT", "ROLE_CUSTOMERMANAGERGROUP" })
    @RequestMapping(value = "/{userId}/unlink-paypal", method = RequestMethod.POST)
    @ResponseStatus(value = HttpStatus.OK)
    @ApiOperation(nickname = "unlinkConnectWithPayPal", value = "Unlink connect with PayPal for current customer", notes = "Unlink connect with PayPal for current customer")
    @ResponseBody
    @ApiBaseSiteIdAndUserIdParam
    public BraintreeUnlinkConnectWithPayPalWsDTO unlinkLoginWithPayPal() {
        BraintreeUnlinkConnectWithPayPalWsDTO unlinkWsDTO = new BraintreeUnlinkConnectWithPayPalWsDTO();
        unlinkWsDTO.setIsAccountUnlinked(brainTreeConnectWithPayPalFacade.unlinkConnectWithPayPalAccount());
        return unlinkWsDTO;
    }

    @Secured({ "ROLE_CLIENT", "ROLE_TRUSTED_CLIENT", "ROLE_CUSTOMERMANAGERGROUP" })
    @RequestMapping(method = RequestMethod.POST, consumes = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
    @ResponseStatus(value = HttpStatus.CREATED)
    @ResponseBody
    @ApiOperation(nickname = "createUser", value = " Registers a customer", notes = "Registers a customer. Requires the following "
            + "parameters: login, password, firstName, lastName, titleCode.")
    @ApiBaseSiteIdParam
    @RequestMappingOverride
    public UserWsDTO createUser(@ApiParam(value = "User's object.", required = true) @RequestBody final UserSignUpWsDTO user,
                                @ApiFieldsParam @RequestParam(defaultValue = DEFAULT_FIELD_SET) final String fields,
                                final HttpServletRequest httpRequest, final HttpServletResponse httpResponse) throws DuplicateUidException {
        validate(user, "user", userSignUpDTOValidator);
        final RegisterData registerData = getDataMapper()
                .map(user, RegisterData.class, "login,password,titleCode,firstName,lastName");
        if (brainTreeUserFacade.isUserExisting(registerData.getLogin().toLowerCase())) {
            throw new DuplicateUidException("User with email " + registerData.getLogin() + " already exists.");
        }
        boolean userExists = false;
        customerFacade.register(registerData);
        final String userId = user.getUid().toLowerCase(Locale.ENGLISH);
        httpResponse.setHeader(BraintreeoccControllerConstants.LOCATION, getAbsoluteLocationURL(httpRequest, userId));
        final CustomerData customerData = getCustomerData(registerData, userExists, userId);
        return getDataMapper().map(customerData, UserWsDTO.class, fields);
    }

    @Secured({"ROLE_CUSTOMERGROUP", "ROLE_TRUSTED_CLIENT", "ROLE_CUSTOMERMANAGERGROUP"})
    @RequestMapping(value = "/{userId}/saveAddress", method = RequestMethod.POST)
    @ResponseStatus(value = HttpStatus.OK)
    @ResponseBody
    @ApiBaseSiteIdAndUserIdParam
    public void saveAddressToAddressBook(@RequestBody final AddressWsDTO billingAddress) {
        final AddressData addressData = getDataMapper().map(billingAddress, AddressData.class, ADDRESS_MAPPING);
        validate(billingAddress, OBJECT_NAME_ADDRESS, getAddressDTOValidator());
        if (brainTreeUserFacade.isDuplicateAddressForCurrentUser(addressData)) {
            LOG.error("Address is already exist");
            throw new BraintreeAddressDuplicatedException();
        }
        brainTreeUserFacade.saveAddressForCurrentCustomer(addressData);

    }

    protected CustomerData getCustomerData(final RegisterData registerData, final boolean userExists, final String userId)
    {
        final CustomerData customerData;
        if (userExists)
        {
            customerData = customerFacade.nextDummyCustomerData(registerData);
        }
        else
        {
            customerData = customerFacade.getUserForUID(userId);
        }
        return customerData;
    }

    protected String getAbsoluteLocationURL(final HttpServletRequest httpRequest, final String uid)
    {
        final String requestURL = httpRequest.getRequestURL().toString();
        final String encodedUid = UriUtils.encodePathSegment(uid, StandardCharsets.UTF_8.name());
        return UriComponentsBuilder.fromHttpUrl(requestURL).pathSegment(encodedUid).build().toString();
    }

    protected boolean containsRole(final Authentication auth, final String role) {
        for (final GrantedAuthority ga : auth.getAuthorities()) {
            if (ga.getAuthority().equals(role)) {
                return true;
            }
        }
        return false;
    }

    protected static String sanitize(final String input)
    {
        return YSanitizer.sanitize(input);
    }
}
