package productcatalog.productdetail;

import com.google.inject.Injector;
import common.contexts.UserContext;
import common.controllers.SunriseFrameworkController;
import common.controllers.SunrisePageData;
import common.controllers.WithOverwriteableTemplateName;
import common.hooks.SunrisePageDataHook;
import io.sphere.sdk.products.ProductProjection;
import io.sphere.sdk.products.ProductVariant;
import io.sphere.sdk.products.queries.ProductProjectionQuery;
import io.sphere.sdk.products.search.ProductProjectionSearch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.libs.concurrent.HttpExecution;
import play.mvc.Result;
import play.twirl.api.Html;
import productcatalog.hooks.ProductProjectionQueryFilterHook;
import productcatalog.hooks.ProductProjectionSearchFilterHook;
import productcatalog.hooks.SingleProductProjectionHook;
import wedecidelatercommon.ProductReverseRouter;

import javax.annotation.Nullable;
import javax.inject.Inject;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletionStage;

import static java.util.Arrays.asList;
import static java.util.concurrent.CompletableFuture.completedFuture;

public abstract class SunriseProductDetailPageController extends SunriseFrameworkController implements WithOverwriteableTemplateName {

    protected static final Logger logger = LoggerFactory.getLogger(SunriseProductDetailPageController.class);

    @Inject
    private UserContext userContext;
    @Inject
    private ProductReverseRouter productReverseRouter;
    @Inject
    private Injector injector;
    @Inject
    protected ProductDetailPageContentFactory productDetailPageContentFactory;

    @Nullable
    private String productSlug;
    @Nullable
    private String variantSku;

    public CompletionStage<Result> showProductBySlugAndSku(final String languageTag, final String slug, final String sku) {
        logger.debug("look for product with slug={} in locale={} and sku={}", slug, languageTag, sku);
        this.productSlug = slug;
        this.variantSku = sku;
        return injector.getInstance(ProductFetchBySlugAndSku.class).findProduct(slug, sku, this::filter, this::filter)
                .thenComposeAsync(this::showProduct, HttpExecution.defaultContext());
    }

    public CompletionStage<Result> showProductByProductIdAndVariantId(final String languageTag, final String productId, final int variantId) {
        logger.debug("look for product with productId={} and variantId={}", productId, variantId);
        return injector.getInstance(ProductFetchByProductIdAndVariantId.class).findProduct(productId, variantId, this::filter, this::filter)
                .thenComposeAsync(this::showProduct, HttpExecution.defaultContext());
    }

    private ProductProjectionQuery filter(ProductProjectionQuery q) {
        return runFilterHook(ProductProjectionQueryFilterHook.class, (hook, r) -> hook.filterProductProjectionQuery(r), q);
    }

    private ProductProjectionSearch filter(ProductProjectionSearch q) {
        return runFilterHook(ProductProjectionSearchFilterHook.class, (hook, r) -> hook.filterProductProjectionSearch(r), q);
    }

    protected Optional<String> getProductSlug() {
        return Optional.ofNullable(productSlug);
    }

    protected Optional<String> getVariantSku() {
        return Optional.ofNullable(variantSku);
    }

    protected CompletionStage<Result> showProduct(final ProductFetchResult productFetchResult) {
        final Optional<ProductProjection> product = productFetchResult.getProduct();
        final Optional<ProductVariant> variant = productFetchResult.getVariant();
        if (product.isPresent() && variant.isPresent()) {
            return handleFoundProduct(product.get(), variant.get());
        } else if (product.isPresent()) {
            return handleNotFoundVariant(product.get());
        } else {
            return handleNotFoundProduct();
        }
    }

    protected CompletionStage<Result> handleFoundProduct(final ProductProjection product, final ProductVariant variant) {
        final CompletionStage<Object> hooksCompletionStage = runAsyncHook(SingleProductProjectionHook.class, hook -> hook.onSingleProductProjectionLoaded(product));
        return hooksCompletionStage.thenApplyAsync(unusedResult -> {
            final ProductDetailPageContent pageContent = createPageContent(product, variant);
            return ok(renderPage(pageContent));
        }, HttpExecution.defaultContext());
    }

    protected CompletionStage<Result> handleNotFoundVariant(final ProductProjection product) {
        return redirectToMasterVariant(product);
    }

    protected CompletionStage<Result> handleNotFoundProduct() {
        if (getProductSlug().isPresent() && getVariantSku().isPresent()) {
            return findNewProductSlug(getProductSlug().get()).thenApplyAsync(newSlugOpt -> newSlugOpt
                    .map(newSlug -> redirectToNewSlug(newSlug, getVariantSku().get()))
                    .orElseGet(this::notFoundProductResult),
                    HttpExecution.defaultContext());
        } else {
            return completedFuture(notFoundProductResult());
        }
    }

    protected ProductDetailPageContent createPageContent(final ProductProjection product, final ProductVariant variant) {
        return productDetailPageContentFactory.create(product, variant);
    }

    protected Html renderPage(final ProductDetailPageContent pageContent) {
        final SunrisePageData pageData = pageData(userContext, pageContent, ctx(), session());
        runVoidHook(SunrisePageDataHook.class, hook -> hook.acceptSunrisePageData(pageData));
        return templateEngine().renderToHtml(getTemplateName(), pageData, userContext.locales());
    }

    @Override
    public String getTemplateName() {
        return "pdp";
    }

    protected Result notFoundProductResult() {
        return notFound();
    }

    private Result redirectToNewSlug(final String newSlug, final String sku) {
        return movedPermanently(productReverseRouter.productDetailPageCall(userContext.locale().toLanguageTag(), newSlug, sku));
    }

    private CompletionStage<Result> redirectToMasterVariant(final ProductProjection product) {
        return productReverseRouter.productDetailPageCall(userContext.locale(), product, product.getMasterVariant())
                .map(call -> completedFuture(redirect(call)))
                .orElseGet(() -> completedFuture(notFoundProductResult()));
    }

    private CompletionStage<Optional<String>> findNewProductSlug(final String slug) {
        return completedFuture(Optional.empty()); // TODO look for messages and find current slug
    }

    @Override
    public Set<String> getFrameworkTags() {
        return new HashSet<>(asList("productdetailpage", "product"));
    }
}
@@ -847,24 +847,78 @@
 ectionHook;%0A
+import productcatalog.hooks.SingleProductVariantHook;%0A
 import wedec
@@ -3784,32 +3784,47 @@
 ndleFoundProduct
+AndCallingHooks
 (product.get(), 
@@ -3831,32 +3831,32 @@
 variant.get());%0A
-
         %7D else i
@@ -4068,24 +4068,39 @@
 FoundProduct
+AndCallingHooks
 (final Produ
@@ -4314,24 +4314,193 @@
 (product));%0A
+        final CompletionStage%3CObject%3E hooksCompletionStage2 = runAsyncHook(SingleProductVariantHook.class, hook -%3E hook.onSingleProductVariantLoaded(product, variant));%0A
         retu
@@ -4526,46 +4526,340 @@
 tage
+2
 .then
-ApplyAsync(unusedResult -%3E %7B%0A    
+ComposeAsync(unused -%3E%0A                hooksCompletionStage.thenComposeAsync(unusedResult -%3E handleFoundProduct(product, variant), HttpExecution.defaultContext()), HttpExecution.defaultContext());%0A    %7D%0A%0A    protected CompletionStage%3CResult%3E handleFoundProduct(final ProductProjection product, final ProductVariant variant) %7B%0A
     
@@ -4952,27 +4952,39 @@
         
-    
 return 
+completedFuture(
 ok(rende
@@ -5006,51 +5006,8 @@
 nt))
-;%0A        %7D, HttpExecution.defaultContext()
 );%0A 

add hook onSingleProductVariantLoaded to PDP