/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.ofbiz.ebaystore;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.ofbiz.base.util.Debug;
import org.apache.ofbiz.base.util.UtilGenerics;

import com.ebay.sdk.ApiContext;
import com.ebay.sdk.ApiException;
import com.ebay.sdk.SdkException;
import com.ebay.sdk.SdkSoapException;
import com.ebay.sdk.attributes.model.AttributeSet;
import com.ebay.sdk.attributes.model.IAttributesMaster;
import com.ebay.sdk.call.GetCategorySpecificsCall;
import com.ebay.sdk.call.GetDescriptionTemplatesCall;
import com.ebay.soap.eBLBaseComponents.BestOfferEnabledDefinitionType;
import com.ebay.soap.eBLBaseComponents.BuyerPaymentMethodCodeType;
import com.ebay.soap.eBLBaseComponents.CategoryFeatureType;
import com.ebay.soap.eBLBaseComponents.CategoryType;
import com.ebay.soap.eBLBaseComponents.DescriptionTemplateType;
import com.ebay.soap.eBLBaseComponents.DetailLevelCodeType;
import com.ebay.soap.eBLBaseComponents.FeatureDefinitionsType;
import com.ebay.soap.eBLBaseComponents.GetDescriptionTemplatesRequestType;
import com.ebay.soap.eBLBaseComponents.GetDescriptionTemplatesResponseType;
import com.ebay.soap.eBLBaseComponents.ItemSpecificsEnabledCodeType;
import com.ebay.soap.eBLBaseComponents.ListingDurationDefinitionType;
import com.ebay.soap.eBLBaseComponents.ListingDurationDefinitionsType;
import com.ebay.soap.eBLBaseComponents.ListingDurationReferenceType;
import com.ebay.soap.eBLBaseComponents.NameRecommendationType;
import com.ebay.soap.eBLBaseComponents.RecommendationsType;
import com.ebay.soap.eBLBaseComponents.SiteDefaultsType;
import com.ebay.soap.eBLBaseComponents.StoreOwnerExtendedListingDurationsType;
import com.ebay.soap.eBLBaseComponents.ThemeGroupType;

public class EbayStoreCategoryFacade {
    private static final String MODULE = EbayStoreCategoryFacade.class.getName();
    private ApiContext apiContext = null;
    private String catId = null;
    private IAttributesMaster attrMaster = null;
    private EbayStoreSiteFacade siteFacade = null;

    private AttributeSet[] joinedAttrSets = null;
    private ItemSpecificsEnabledCodeType itemSpecificEnabled = null;
    private Boolean retPolicyEnabled = null;
    private Map<Integer, String[]> listingDurationMap = null;
    private Map<String, Integer> listingDurationReferenceMap = null;
    private BuyerPaymentMethodCodeType[] paymentMethods = null;
    private NameRecommendationType[] nameRecommendationTypes = null;
    private StoreOwnerExtendedListingDurationsType storeOwnerExtendedListingDuration = null;
    private BestOfferEnabledDefinitionType bestOfferEnabled = null;
    private List<Map<String, Object>> adItemTemplates = null;

    public EbayStoreCategoryFacade(String catId, ApiContext apiContext, IAttributesMaster attrMaster, EbayStoreSiteFacade siteFacade) throws SdkException, Exception {
        this.catId = catId;
        this.apiContext = apiContext;
        this.attrMaster = attrMaster;
        this.siteFacade = siteFacade;
        this.syncCategoryMetaData();
    }

    private void syncCategoryMetaData() throws SdkException, Exception {
        syncJoinedAttrSets();
        syncCategoryFeatures();
        syncNameRecommendationTypes();
        syncAdItemTemplates();
    }

    private void syncJoinedAttrSets() throws SdkException, Exception {
        int[] ids = new int[1];
        ids[0] = Integer.parseInt(this.getCatId());
        AttributeSet[] itemSpecAttrSets = attrMaster.getItemSpecificAttributeSetsForCategories(ids);
        AttributeSet[] siteWideAttrSets = attrMaster.getSiteWideAttributeSetsForCategories(ids);
        AttributeSet[] joinedAttrSets = attrMaster.joinItemSpecificAndSiteWideAttributeSets(itemSpecAttrSets, siteWideAttrSets);
        this.joinedAttrSets = joinedAttrSets;
    }

    private void syncNameRecommendationTypes() throws ApiException, SdkException, Exception {
        GetCategorySpecificsCall getCatSpe = new GetCategorySpecificsCall(apiContext);
        getCatSpe.setCategoryID(new String[]{this.catId});
        DetailLevelCodeType[] detailLevels = new DetailLevelCodeType[] {DetailLevelCodeType.RETURN_ALL};
        getCatSpe.setDetailLevel(detailLevels);
        RecommendationsType[] recommendationsArray = getCatSpe.getCategorySpecifics();
        if (recommendationsArray == null || recommendationsArray.length == 0)
            return;
        RecommendationsType recommendations = recommendationsArray[0];
        this.nameRecommendationTypes = recommendations.getNameRecommendation();
    }

    public void syncCategoryFeatures() throws Exception {
        Map<String, CategoryType> categoriesCacheMap = this.siteFacade.getSiteCategoriesMap().get(apiContext.getSite());

        Map<String, CategoryFeatureType> cfsMap = this.siteFacade.getSiteCategoriesFeaturesMap().get(apiContext.getSite());
        SiteDefaultsType siteDefaults = this.siteFacade.getSiteFeatureDefaultMap().get(apiContext.getSite());
        FeatureDefinitionsType featureDefinition = this.siteFacade.getSiteFeatureDefinitionsMap().get(apiContext.getSite());

        //get itemSpecificsEnabled feature
        itemSpecificEnabled = (ItemSpecificsEnabledCodeType) getInheritProperty(catId, "getItemSpecificsEnabled", categoriesCacheMap, cfsMap);
        if (itemSpecificEnabled == null) {
            itemSpecificEnabled = siteDefaults.getItemSpecificsEnabled();
        }
        //get returnPolicyEnabled feature
        retPolicyEnabled = (Boolean) getInheritProperty(catId, "isReturnPolicyEnabled", categoriesCacheMap, cfsMap);
        if (retPolicyEnabled == null) {
            retPolicyEnabled = siteDefaults.isReturnPolicyEnabled();
        }

        //get listing durations
        ListingDurationDefinitionsType listDuration = featureDefinition.getListingDurations();
        ListingDurationDefinitionType[] durationArray = listDuration.getListingDuration();
        listingDurationMap = new HashMap<>();
        for (ListingDurationDefinitionType listingDurationDefinitionType : durationArray) {
            listingDurationMap.put(listingDurationDefinitionType.getDurationSetID(), listingDurationDefinitionType.getDuration());
        }

        //get listing types
        ListingDurationReferenceType[] listingDuration = (ListingDurationReferenceType[]) getInheritProperty(catId, "getListingDuration", categoriesCacheMap, cfsMap);
        if (listingDuration == null || listingDuration.length == 0) {
            listingDuration = siteDefaults.getListingDuration();
        }
        listingDurationReferenceMap = new HashMap<>();
        for (ListingDurationReferenceType listingDurationReferenceType : listingDuration) {
            listingDurationReferenceMap.put(listingDurationReferenceType.getType().value(), listingDurationReferenceType.getValue());
        }

        //get payment methods
        paymentMethods = (BuyerPaymentMethodCodeType[]) getInheritProperty(catId, "getPaymentMethod", categoriesCacheMap, cfsMap);
        if (paymentMethods == null || paymentMethods.length == 0) {
            paymentMethods = siteDefaults.getPaymentMethod();
        }

        //fix 'invalid enum' issue
        paymentMethods = fiterPaymentMethod(paymentMethods);

        storeOwnerExtendedListingDuration = siteDefaults.getStoreOwnerExtendedListingDurations();

        bestOfferEnabled = featureDefinition.getBestOfferEnabled();
    }

    //remove all 'null' code type
    private static BuyerPaymentMethodCodeType[] fiterPaymentMethod(BuyerPaymentMethodCodeType[] paymentMethods) {
        List<BuyerPaymentMethodCodeType> al = new ArrayList<BuyerPaymentMethodCodeType>();
        for (BuyerPaymentMethodCodeType pm : paymentMethods) {
            if (pm != null) {
                al.add(pm);
            }
        }
        return al.toArray(new BuyerPaymentMethodCodeType[0]);
    }

    /**
     * recursively check the parent category to find out category feature
     * @param catId categoryID to be retrieved
     * @param methodName method name to be invoked
     * @param categoriesCacheMap cache of all the categories
     * @param cfsMap category features map
     * @return generic Object
     * @throws Exception
     */
    private Object getInheritProperty(String catId, String methodName,
            Map<String, CategoryType> categoriesCacheMap, Map<String, CategoryFeatureType> cfsMap) throws Exception {
        if (cfsMap.containsKey(catId)) {
            CategoryFeatureType cf = cfsMap.get(catId);
            // invoke the method indicated by methodName
            Object returnValue = invokeMethodByName(cf, methodName);
            if (returnValue != null) {
                return returnValue;
            }
        }

        CategoryType cat = categoriesCacheMap.get(catId);
        //if we reach top level, return null
        if (cat.getCategoryLevel() == 1) {
            return null;
        }

        //check parent category
        return getInheritProperty(cat.getCategoryParentID(0), methodName, categoriesCacheMap, cfsMap);
    }

    /**
     * invoke the method specified by methodName and return the corresponding return value
     * @param cf CategoryFeatureType
     * @param methodName String
     * @return generic object
     */
    private Object invokeMethodByName(CategoryFeatureType cf, String methodName) {
        java.lang.reflect.Method m = null;
        try {
            m = cf.getClass().getMethod(methodName);
            if (m != null) {
                return m.invoke(cf);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public List<Map<String, Object>> syncAdItemTemplates() throws ApiException, SdkSoapException, SdkException {
        GetDescriptionTemplatesRequestType req = null;
        GetDescriptionTemplatesResponseType resp = null;
        List<Map<String, Object>> temGroupList = new LinkedList<>();

        GetDescriptionTemplatesCall call = new GetDescriptionTemplatesCall(this.apiContext);
        req = new GetDescriptionTemplatesRequestType();
        req.setCategoryID(this.catId);
        resp = (GetDescriptionTemplatesResponseType) call.execute(req);
        if (resp != null && "SUCCESS".equals(resp.getAck().toString())) {
            DescriptionTemplateType[] descriptionTemplateTypeList = resp.getDescriptionTemplate();
            Debug.logInfo("layout of category "+ this.catId +":"+ resp.getLayoutTotal(), MODULE);
            for (DescriptionTemplateType descTemplateType : descriptionTemplateTypeList) {
                List<Map<String, Object>> templateList = null;
                Map<String, Object> templateGroup = null;
                if ("THEME".equals(String.valueOf(descTemplateType.getType()))) {
                    Map<String, Object> template = new HashMap<>();
                    template.put("TemplateId", String.valueOf(descTemplateType.getID()));
                    template.put("TemplateImageURL", descTemplateType.getImageURL());
                    template.put("TemplateName", descTemplateType.getName());
                    template.put("TemplateType", descTemplateType.getType());

                    // check group template by groupId
                    for (Map<String, Object> temGroup : temGroupList) {
                        if (temGroup.get("TemplateGroupId").equals(descTemplateType.getGroupID().toString())) {
                            templateGroup = temGroup;
                            break;
                        }
                    }
                    if (templateGroup == null) {
                        templateGroup = new HashMap<>();
                        templateList = new LinkedList<>();
                        templateGroup.put("TemplateGroupId", descTemplateType.getGroupID().toString());
                        templateList.add(template);
                        templateGroup.put("Templates", templateList);
                        temGroupList.add(templateGroup);
                    } else {
                        if (templateGroup.get("Templates") != null) {
                            templateList = UtilGenerics.checkList(templateGroup.get("Templates"));
                            templateList.add(template);
                        }
                    }
                } else if ("Layout".equals(String.valueOf(descTemplateType.getType()))) {
                }
            }
            ThemeGroupType[] themes = resp.getThemeGroup();
            if (themes != null && temGroupList != null) {
                for (Map<String, Object> temGroup : temGroupList) {
                    for (ThemeGroupType theme : themes) {
                        if (theme.getGroupID() == Integer.parseInt(temGroup.get("TemplateGroupId").toString())) {
                            if (theme != null) temGroup.put("TemplateGroupName", theme.getGroupName());
                            break;
                        } else {
                            if (theme != null) temGroup.put("TemplateGroupName", "_NA_");
                        }
                    }
                }
            }
        }
        return adItemTemplates = temGroupList;
    }

    public List<Map<String, Object>> getAdItemTemplates(String temGroupId) {
        List<Map<String, Object>> themes = new LinkedList<>();
        for (Map<String, Object> temp : this.adItemTemplates) {
            if (temp.get("TemplateGroupId").equals(temGroupId)) {
                themes = UtilGenerics.checkList(temp.get("Templates"));
                break;
            }
        }
        return themes;
    }

    public String getCatId() {
        return catId;
    }

    public void setCatId(String catId) {
        this.catId = catId;
    }

    public AttributeSet[] getJoinedAttrSets() {
        return joinedAttrSets;
    }

    public ItemSpecificsEnabledCodeType getItemSpecificEnabled() {
        return itemSpecificEnabled;
    }

    public Boolean getRetPolicyEnabled() {
        return retPolicyEnabled;
    }

    public Map<Integer, String[]> getListingDurationMap() {
        return listingDurationMap;
    }

    public Map<String, Integer> getListingDurationReferenceMap() {
        return listingDurationReferenceMap;
    }

    public BuyerPaymentMethodCodeType[] getPaymentMethods() {
        return paymentMethods;
    }

    public NameRecommendationType[] getNameRecommendationTypes() {
        return nameRecommendationTypes;
    }

    public boolean AttributesEnabled() {
        return this.joinedAttrSets != null && this.joinedAttrSets.length > 0;
    }

    public StoreOwnerExtendedListingDurationsType getStoreOwnerExtendedListingDuration() {
        return this.storeOwnerExtendedListingDuration;
    }

    public BestOfferEnabledDefinitionType getbestOfferEnabled() {
        return this.bestOfferEnabled;
    }

    public List<Map<String, Object>> getAdItemTemplates() {
        return this.adItemTemplates;
    }
}
