package com.salesmanager.core.business.services.catalog.category;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import com.salesmanager.core.business.constants.Constants;
import com.salesmanager.core.business.exception.ServiceException;
import com.salesmanager.core.business.repositories.catalog.category.CategoryRepository;
import com.salesmanager.core.business.services.catalog.product.ProductService;
import com.salesmanager.core.business.services.common.generic.SalesManagerEntityServiceImpl;
import com.salesmanager.core.model.catalog.category.Category;
import com.salesmanager.core.model.catalog.category.CategoryDescription;
import com.salesmanager.core.model.catalog.product.Product;
import com.salesmanager.core.model.merchant.MerchantStore;
import com.salesmanager.core.model.reference.language.Language;

import br.ufrgs.inf.prosoft.cache.SingleCache;
import br.ufrgs.inf.prosoft.cache.Parameters;

@Service("categoryService")
public class CategoryServiceImpl extends SalesManagerEntityServiceImpl<Long, Category> implements CategoryService {
	
	 private CategoryRepository categoryRepository;
	
	 @PersistenceContext(unitName = "shopizerContainer")
	 private EntityManager em;
	  
	 @Inject
	 private ProductService productService;
	  

	
	@Inject
	public CategoryServiceImpl(CategoryRepository categoryRepository) {
		super(categoryRepository);
		this.categoryRepository = categoryRepository;
	}
	
	public void create(Category category) throws ServiceException {
		
		super.create(category);
		
		StringBuilder lineage = new StringBuilder();
		Category parent = category.getParent();
		if(parent!=null && parent.getId()!=null && parent.getId()!=0) {
			lineage.append(parent.getLineage()).append("/").append(parent.getId());
			category.setDepth(parent.getDepth()+1);
		} else {
			lineage.append("/");
			category.setDepth(0);
		}
		category.setLineage(lineage.toString());
		super.update(category);
		
		
	}
	
	@Override
	public List<Object[]> countProductsByCategories(MerchantStore store,
			List<Long> categoryIds) throws ServiceException {
		
		return categoryRepository.countProductsByCategories(store, categoryIds);
		
	}
	
	@Override
	public List<Category> listByCodes(MerchantStore store, List<String> codes,
			Language language) {
		return categoryRepository.findByCodes(store.getId(), codes, language.getId());
	}
	
	@Override
	public List<Category> listByIds(MerchantStore store, List<Long> ids,
			Language language) {
		return categoryRepository.findByIds(store.getId(), ids, language.getId());
	}
	
	@Override
	public Category getByLanguage(long categoryId, Language language) {
		return categoryRepository.findById(categoryId, language.getId());
	}
	
	@Override
	public void saveOrUpdate(Category category) throws ServiceException {
		
		
		//save or update (persist and attach entities
		if(category.getId()!=null && category.getId()>0) {

			super.update(category);
			
		} else {
			
			super.save(category);
			
		}
		
	}

	@Override
	public List<Category> listByLineage(MerchantStore store, String lineage) throws ServiceException {
		try {
			return categoryRepository.findByLineage(store.getId(), lineage);
		} catch (Exception e) {
			throw new ServiceException(e);
		}
		
		
	}
	
	@Override
	public List<Category> listByLineage(String storeCode, String lineage) throws ServiceException {
		try {
			return categoryRepository.findByLineage(storeCode, lineage);
		} catch (Exception e) {
			throw new ServiceException(e);
		}
		
		
	}
	

	@Override
	public List<Category> listBySeUrl(MerchantStore store, String seUrl) throws ServiceException{
		
		try {
			return categoryRepository.listByFriendlyUrl(store.getId(), seUrl);
		} catch (Exception e) {
			throw new ServiceException(e);
		}
		
	}
	
	@Override
	public Category getBySeUrl(MerchantStore store,String seUrl) {
		return categoryRepository.findByFriendlyUrl(store.getId(), seUrl);
	}
	
	
	@Override
	public Category getByCode(MerchantStore store, String code) throws ServiceException {
		
		try {
			return categoryRepository.findByCode(store.getId(), code);
		} catch (Exception e) {
			throw new ServiceException(e);
		}
		
	}
	
	@Override
	public Category getByCode(String storeCode, String code) throws ServiceException {
		
		try {
			return categoryRepository.findByCode(storeCode, code);
		} catch (Exception e) {
			throw new ServiceException(e);
		}
		
	}
	
	
	@Override
	public Category getById(Long id) {

			return categoryRepository.findOne(id);
		
	}
	
	@Override
	public List<Category> listByParent(Category category) throws ServiceException {
		
		try {
			return categoryRepository.listByStoreAndParent(null, category);
		} catch (Exception e) {
			throw new ServiceException(e);
		}
		
	}
	
	@Override
	public List<Category> listByStoreAndParent(MerchantStore store, Category category) throws ServiceException {
		
		try {
			return categoryRepository.listByStoreAndParent(store, category);
		} catch (Exception e) {
			throw new ServiceException(e);
		}
		
	}
	
	@Override
	public List<Category> listByParent(Category category, Language language) {
		Assert.notNull(category, "Category cannot be null");
		Assert.notNull(language, "Language cannot be null");
		Assert.notNull(category.getMerchantStore(), "category.merchantStore cannot be null");
		
		return categoryRepository.findByParent(category.getId(), language.getId());
	}

	
	@Override
	public void addCategoryDescription(Category category, CategoryDescription description)
			throws ServiceException {
		
		
		
		try {
			category.getDescriptions().add(description);
			description.setCategory(category);
			update(category);
		} catch (Exception e) {
			throw new ServiceException(e);
		}

	}

	
	//@Override
	public void delete(Category category) throws ServiceException {
		
		//get category with lineage (subcategories)
		StringBuilder lineage = new StringBuilder();
		lineage.append(category.getLineage()).append(category.getId()).append(Constants.SLASH);
		List<Category> categories = this.listByLineage(category.getMerchantStore(), lineage.toString());
		
		Category dbCategory = this.getById( category.getId() );
		
		
		if(dbCategory != null && dbCategory.getId().longValue() == category.getId().longValue() ) {			
			
			
			categories.add(dbCategory);
			
			
			Collections.reverse(categories);
			
			List<Long> categoryIds = new ArrayList<Long>();
	
				
			for(Category c : categories) {		
					categoryIds.add(c.getId());
			}

			List<Product> products = productService.getProducts(categoryIds);
			org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);//need to refresh the session to update all product categories

			
			for(Product product : products) {
				session.evict(product);//refresh product so we get all product categories
				Product dbProduct = productService.getById(product.getId());
				Set<Category> productCategories = dbProduct.getCategories();
				if(productCategories.size()>1) {
					for(Category c : categories) {
						productCategories.remove(c);
						productService.update(dbProduct);
					}
					
					if(product.getCategories()==null || product.getCategories().size()==0) {
						productService.delete(dbProduct);
					}
					
				} else {
					productService.delete(dbProduct);
				}
				
				
			}
			
			Category categ = this.getById(category.getId());
			categoryRepository.delete(categ);
			
		}
		
	}



	@Override
	public CategoryDescription getDescription(Category category, Language language) {
		
		
		for (CategoryDescription description : category.getDescriptions()) {
			if (description.getLanguage().equals(language)) {
				return description;
			}
		}
		return null;
	}
	
	@Override
	public void addChild(Category parent, Category child) throws ServiceException {
		
		
		if(child==null || child.getMerchantStore()==null) {
			throw new ServiceException("Child category and merchant store should not be null");
		}
		
		try {
			
			if(parent==null) {
				
				//assign to root
				child.setParent(null);
				child.setDepth(0);
				//child.setLineage(new StringBuilder().append("/").append(child.getId()).append("/").toString());
				child.setLineage("/");
				
			} else {
				
				Category p = this.getById(parent.getId());//parent
				
				

				
				String lineage = p.getLineage();
				int depth = p.getDepth();//TODO sometimes null
				
				child.setParent(p);
				child.setDepth(depth+1);
				child.setLineage(new StringBuilder().append(lineage).append(p.getId()).append("/").toString());
				
				
			}
			

			update(child);
			StringBuilder childLineage = new StringBuilder();
			childLineage.append(child.getLineage()).append(child.getId()).append("/");
			List<Category> subCategories = listByLineage(child.getMerchantStore(),childLineage.toString());
			
			
			//ajust all sub categories lineages
			if(subCategories!=null && subCategories.size()>0) {
				for(Category subCategory : subCategories) {
					if(child.getId()!=subCategory.getId()) {
						addChild(child, subCategory);
					}
				}
				
			}
		} catch (Exception e) {
			throw new ServiceException(e);
		}
		

	}
	
public static SingleCache<Parameters, List<Category>> listByDepthCache1 = new SingleCache<>("static-single:25#CategoryServiceImpl.listByDepth1");

	@Override
	public List<Category> listByDepth(MerchantStore store, int depth) {
return listByDepthCache1.computeIfAbsent(new Parameters(store, depth),  () -> {
		return categoryRepository.findByDepth(store.getId(), depth);
}, 1200000);
	}
	
public static SingleCache<Parameters, List<Category>> listByDepthCache2 = new SingleCache<>("static-single:25#CategoryServiceImpl.listByDepth2");
	@Override
	public List<Category> listByDepth(MerchantStore store, int depth, Language language) {
return listByDepthCache2.computeIfAbsent(new Parameters(store, depth, language),  () -> {
		return categoryRepository.findByDepth(store.getId(), depth, language.getId());
}, 1200000);
	}

	@Override
	public List<Category> getByName(MerchantStore store, String name, Language language) throws ServiceException {
		
		try {
			return categoryRepository.findByName(store.getId(), name, language.getId());
		} catch (Exception e) {
			throw new ServiceException(e);
		}
		
		
	}
	
	

	@Override
	public List<Category> listByStore(MerchantStore store)
			throws ServiceException {

		try {
			return categoryRepository.findByStore(store.getId());
		} catch (Exception e) {
			throw new ServiceException(e);
		}
	}
	
	@Override
	public List<Category> listByStore(MerchantStore store, Language language)
			throws ServiceException {

		try {
			return categoryRepository.findByStore(store.getId(), language.getId());
		} catch (Exception e) {
			throw new ServiceException(e);
		}
	}
	
	


}
