package productcatalog.controllers;
import common.cms.CmsPage;
import common.contexts.UserContext;
import common.controllers.ControllerDependency;
import common.controllers.SunriseController;
import common.pages.*;
import common.utils.PriceFormatter;
import io.sphere.sdk.categories.Category;
import io.sphere.sdk.products.ProductProjection;
import io.sphere.sdk.products.ProductVariant;
import play.libs.F;
import play.mvc.Result;
import productcatalog.models.ProductNotFoundException;
import productcatalog.models.ProductVariantNotFoundException;
import productcatalog.models.ShopShippingRate;
import productcatalog.pages.*;
import productcatalog.services.CategoryService;
import productcatalog.services.ProductProjectionService;
import productcatalog.services.ShippingMethodService;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.List;
import java.util.Locale;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
@Singleton
public class ProductDetailPageController extends SunriseController {
private final int numberOfSuggestions;
private final ProductProjectionService productService;
private final CategoryService categoryService;
private final ShippingMethodService shippingMethodService;
@Inject
public ProductDetailPageController(final ControllerDependency controllerDependency, final ProductProjectionService productService, final CategoryService categoryService, final ShippingMethodService shippingMethodService) {
super(controllerDependency);
this.productService = productService;
this.categoryService = categoryService;
this.shippingMethodService = shippingMethodService;
this.numberOfSuggestions = configuration().getInt("pdp.productSuggestions.count");
}
public F.Promise<Result> show(final String locale, final String slug, final String sku) {
final UserContext userContext = userContext(locale);
final F.Promise<CmsPage> cmsPagePromise = cmsService().getPage(userContext.locale(), "pdp");
final F.Promise<ProductProjection> productProjectionPromise = fetchProduct(userContext.locale(), slug);
final F.Promise<List<ProductProjection>> suggestionPromise = productProjectionPromise.flatMap(this::fetchSuggestions);
final F.Promise<Result> resultPromise = productProjectionPromise.flatMap(productProjection ->
cmsPagePromise.flatMap(cms ->
suggestionPromise.map(suggestions ->
getPdpResult(userContext, cms, suggestions, productProjection, sku))));
return recover(resultPromise);
}
private F.Promise<ProductProjection> fetchProduct(final Locale locale, final String slug) {
return productService.searchProductBySlug(locale, slug)
.map(productOptional -> productOptional
.orElseThrow(() -> ProductNotFoundException.bySlug(locale, slug)));
}
private F.Promise<List<ProductProjection>> fetchSuggestions(final ProductProjection productProjection) {
final List<Category> siblingCategories = categoryService.getSiblingCategories(productProjection.getCategories());
return productService.getSuggestions(siblingCategories, numberOfSuggestions);
}
private F.Promise<Result> recover(final F.Promise<Result> resultPromise) {
return resultPromise.recover(exception -> {
if (exception instanceof ProductNotFoundException || exception instanceof ProductVariantNotFoundException) {
return notFoundAction();
} else {
throw exception;
}
});
}
private Result notFoundAction() {
return notFound();
}
private Result getPdpResult(final UserContext userContext, final CmsPage cms, final List<ProductProjection> suggestions,
final ProductProjection productProjection, final String sku) {
final ProductVariant productVariant = getProductVariantBySku(sku, productProjection);
final ProductDetailPageContent content = getProductDetailPageContent(userContext, cms, suggestions, productProjection, productVariant);
final SunrisePageData pageData = pageData(userContext, content);
return ok(templateService().renderToHtml("pdp", pageData));
}
private ProductVariant getProductVariantBySku(final String sku, final ProductProjection productProjection) {
return productProjection.findVariantBySky(sku).orElseThrow(() -> ProductVariantNotFoundException.bySku(sku));
}
/* Methods to build page content */
private ProductDetailPageContent getProductDetailPageContent(final UserContext userContext, final CmsPage cms,
final List<ProductProjection> suggestions,
final ProductProjection productProjection,
final ProductVariant productVariant) {
final String additionalTitle = productProjection.getName().find(userContext.locales()).orElse("");
final PdpStaticData staticData = getStaticData(cms);
final List<SelectableLinkData> breadcrumbData = getBreadcrumbData(userContext, productProjection);
final ProductData productData = getProductData(userContext, productProjection, productVariant);
final List<ShippingRateData> deliveryData = getDeliveryData(userContext);
final List<ProductData> suggestionData = getSuggestionData(userContext, suggestions);
final String formAction = reverseRouter().productVariantToCartForm(userContext.locale().toLanguageTag()).url();
return new ProductDetailPageContent(additionalTitle, staticData, breadcrumbData, productData, deliveryData, suggestionData, formAction);
}
private PdpStaticData getStaticData(final CmsPage cms) {
return new PdpStaticData(cms, BagItemDataFactory.of().create(100), RatingDataFactory.of(cms).create());
}
private List<SelectableLinkData> getBreadcrumbData(final UserContext userContext, final ProductProjection productProjection) {
final BreadcrumbDataFactory breadcrumbDataFactory = BreadcrumbDataFactory.of(reverseRouter(), userContext.locale());
final List<Category> breadcrumbCategories = getBreadcrumbsForProduct(productProjection);
return breadcrumbDataFactory.create(breadcrumbCategories);
}
private List<Category> getBreadcrumbsForProduct(final ProductProjection product) {
return product.getCategories().stream().findFirst()
.map(categoryService::getBreadCrumbCategories)
.orElse(emptyList());
}
private ProductData getProductData(final UserContext userContext, final ProductProjection productProjection,
final ProductVariant productVariant) {
final ProductDataFactory productDataFactory = ProductDataFactory.of(userContext, reverseRouter());
return productDataFactory.create(productProjection, productVariant);
}
private List<ShippingRateData> getDeliveryData(final UserContext userContext) {
final PriceFormatter priceFormatter = priceFormatter(userContext);
final ShippingRateDataFactory shippingRateDataFactory = ShippingRateDataFactory.of(priceFormatter);
return getShippingRates().stream()
.map(shippingRateDataFactory::create)
.collect(toList());
}
private List<ProductData> getSuggestionData(final UserContext userContext, final List<ProductProjection> suggestions) {
final ProductDataFactory productDataFactory = ProductDataFactory.of(userContext, reverseRouter());
return suggestions.stream()
.map((product) -> productDataFactory.create(product, product.getMasterVariant()))
.collect(toList());
}
private List<ShopShippingRate> getShippingRates() {
return emptyList();
}
}