/*******************************************************************************
 * Copyright 2Fi Business Solutions Ltd.
 *
 * This code is copyrighted. Under no circumstances should any party, people,
 * or organization should redistribute any portions of this code in any form,
 * either verbatim or through electronic media, to any third parties, unless
 * under explicit written permission by 2Fi Business Solutions Ltd.
 ******************************************************************************/
package com.ffii.tbms.customer.service;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import com.ffii.core.dao.JdbcDao;
import com.ffii.core.utils.BooleanUtils;
import com.ffii.core.utils.MapUtils;
import com.ffii.core.utils.Params;
import com.ffii.core.utils.SecurityUtils;
import com.ffii.core.web.AbstractService;
import com.ffii.tbms.customer.Customer;
import com.ffii.tbms.customer.dao.CustomerDao;
import com.ffii.tbms.order.Order;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author Patrick
 */
@Service
public class CustomerService extends AbstractService {

	@Autowired
	private JdbcDao jdbcDao;

	@Autowired
	private CustomerDao customerDao;

	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
	public Integer saveOrUpdate(Customer instance) {
		return customerDao.saveOrUpdate(instance);
	}

	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
	public Customer find(Integer id) {
		return customerDao.find(id);
	}

	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
	public List<Map<String, Object>> searchForCombo(Map<String, Object> args) {
		
		SecurityUtils.authArgs(args);

		StringBuilder sql = new StringBuilder(" SELECT "
		+" c.id, "
		+" c.phone1, "
		+" c.phone2, "
		+" concat_ws(' / ', c.phone1, c.phone2) AS phone, "
		+" CONCAT(IF(c.firstName IS NULL, '', CONCAT(c.firstName, ' ')), "
				+" IF(c.surname IS NULL, '', CONCAT(c.surname, ' ')), "
				+" IF(c.nameCh IS NULL, '', c.nameCh)) AS customerName "
		+" FROM customer c WHERE deleted = 0 ");

		if (args.containsKey(Params.QUERY))
			sql.append(" AND ( concat_ws(' ', c.firstName ,c.surname, c.nameCh, c.phone1, c.phone2) LIKE :query "
			+" OR concat_ws(' ', c.surname, c.firstName, c.nameCh, phone1, phone2) LIKE :query ) ");

		if (args.containsKey(Params.ID))
			sql.append(" AND c.id = :id");

		if(args.containsKey("sysGroupId")){
			sql.append(" AND c.sysGroupId = :sysGroupId ");
		}

		sql.append(" ORDER BY c.surname, c.firstName ");

		return jdbcDao.queryForList(sql.toString(), args);
	}

	private String getSearchCriteriaString(Map<String, Object> args){
		StringBuilder sql = new StringBuilder("");

		if (args != null) {
			if (args.containsKey(Params.ID))
				sql.append(" AND c.id = :id");
			if (args.containsKey("surname"))
				sql.append(" AND c.surname LIKE :surname ");
			if (args.containsKey("firstName"))
				sql.append(" AND c.firstName LIKE :firstName ");
			if (args.containsKey("nameCh"))
				sql.append(" AND c.nameCh LIKE :nameCh ");
			if(args.containsKey("sysGroupId")){
				sql.append(" AND c.sysGroupId = :sysGroupId ");
			}
			if (args.containsKey("contact"))
				sql.append(" AND (c.phone1 LIKE :contact OR c.phone2 LIKE :contact "
						+ " OR c.address LIKE :contact OR c.companyPhone LIKE :contact "
						+ " OR c.companyAddress LIKE :contact ) ");
			if (args.containsKey("email"))
				sql.append(" AND (c.email LIKE :email OR c.companyEmail LIKE :email ) ");

			if (args.containsKey("tag")){
				String str = args.get("tag").toString().trim();
				String[] array = str.split(" ");
				str = "";
				for(String tag: array){
					if(!str.equals("")){
						str = str+" OR ";
					}
					str = str+" c.tag LIKE '%"+tag+"%' ";
				}
				sql.append(" AND ( "+str+" ) ");
			}

			//search customer order
			if((args.containsKey("onHand") && BooleanUtils.isTrue(args.get("onHand")))
		 		|| args.containsKey("orderType")
				|| args.containsKey("orderCode")
				|| args.containsKey("fromDate")
				|| args.containsKey("toDate")){

				sql.append(" AND EXISTS( SELECT 1 FROM orders o "
				+" WHERE o.custId = c.id "
				+" AND o.deleted = 0 "
				+" AND o.status != "+Order.STATUS_CANCELLED+" ");

				if (args.containsKey("onHand") && BooleanUtils.isTrue(args.get("onHand")))
					sql.append(" AND o.status = "+Order.STATUS_ON_HAND+" ");
				if (args.containsKey("orderType"))
					sql.append(" AND o.type = :orderType ");
				if (args.containsKey("orderCode"))
					sql.append(" AND o.code LIKE :orderCode ");
				if (args.containsKey("fromDate"))
					sql.append(" AND o.targetCompleteDate >= :fromDate ");
				if (args.containsKey("toDate"))
					sql.append(" AND o.targetCompleteDate < :toDate ");
				
				sql.append(" ) ");
			}

			//search isBalance
			if (args.containsKey("balance") && BooleanUtils.isTrue(args.get("balance")))
					sql.append(" AND IFNULL(custOrders.amount,0) > IFNULL(custPayment.amount,0) ");
			
		}

		return sql.toString();
	}

	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
	public List<Map<String, Object>> search(Map<String, Object> args) {
		
		SecurityUtils.authArgs(args);

		StringBuilder sql = new StringBuilder(
			" SELECT "
			+" c.id,"
			+" c.surname, "
			+" c.firstName, "
			+" c.nameCh, "
			+" CONCAT(IF(c.firstName IS NULL, '', CONCAT(c.firstName, ' ')), "
				+" IF(c.surname IS NULL, '', CONCAT(c.surname, ' ')), "
				+" IF(c.nameCh IS NULL, '', c.nameCh)) AS customerName, "
			+" c.phone1, "
			+" c.phone2, "
			+" c.companyPhone, "
			+" c.companyEmail, "
			+" c.companyAddress, "
			+" c.email, "
			+" c.address, "
			+" c.remarks, "
			+" c.tag, "
			+" lastMeetingDate.date As lastMeetingDate, "
			+" nextMeetingDate.date AS nextMeetingDate, "
			+" refCust.customerName AS refCustomerName, "
			+" IFNULL(custOrders.amount,0) AS orderAmount, "
			+" IFNULL(custPayment.amount,0) AS paymentAmount, "
			+" (IFNULL(custOrders.amount,0) <= IFNULL(custPayment.amount,0)) AS isBalance, "
			+" minTargetDate.targetCompleteDate AS targetCompleteDate,"
			+" custOrders.onHandOrderNum "
		+" FROM "
			+" customer c "
			+" 	LEFT JOIN "
			+" (SELECT "
			+" CONCAT(IF(_c.firstName IS NULL, '', CONCAT(_c.firstName, ' ')), "
				+" IF(_c.surname IS NULL, '', CONCAT(_c.surname, ' ')), "
				+" IF(_c.nameCh IS NULL, '', _c.nameCh)) AS customerName, "
			+" _c.id "
			+" FROM customer _c ) refCust ON refCust.id = c.ref "

			+" LEFT JOIN "
			+" (SELECT "
			+" o.custId, SUM(oi.price) AS amount, "
			+" SUM(IF(o.status = "+Order.STATUS_ON_HAND+", 1, 0)) AS onHandOrderNum "
			+" FROM"
			+" 	orders o "
			+" LEFT JOIN order_item oi ON oi.orderId = o.id "
			+" WHERE "
			+" 	o.deleted = 0 AND oi.deleted = 0"
			+" 		AND o.status != - 1"
			+" GROUP BY o.custId) custOrders ON custOrders.custId = c.id"

			+" 	LEFT JOIN"
			+" (SELECT "
			+" 	o.custId, SUM(p.amount) AS amount"
			+" FROM"
			+" 	orders o"
			+" LEFT JOIN payment p ON p.orderId = o.id"
			+" WHERE"
			+" 	o.deleted = 0 AND p.deleted = 0 "
			//+" AND o.ignoreC = 0 "
			+" 		AND o.status != - 1 "
			+" GROUP BY o.custId) custPayment ON custPayment.custId = c.id"

			+" LEFT JOIN "
			+" ( SELECT Max(m.date) AS date, m.custId "
			+" FROM meeting m "
			+" WHERE m.deleted = 0 "
			+" AND DATE(m.date) < DATE(current_date()) "
			+" GROUP BY m.custId ) lastMeetingDate ON lastMeetingDate.custId = c.id "
			
			+" LEFT JOIN "
			+" ( SELECT MIN(m.date) AS date, m.custId "
			+" FROM meeting m "
			+" WHERE m.deleted = 0 "
			+" AND DATE(m.date) >= DATE(current_date()) "
			+" GROUP BY m.custId ) nextMeetingDate ON nextMeetingDate.custId = c.id "

			+"LEFT JOIN "
			+" ( SELECT o.custId, "
			+" MIN(o.targetCompleteDate) AS targetCompleteDate "
			+" FROM orders o "
			+" WHERE o.deleted = 0 "
			+" AND o.status = "+Order.STATUS_ON_HAND+" "
			+" AND o.targetCompleteDate IS NOT NULL "
			+" GROUP BY o.custId) minTargetDate ON minTargetDate.custId = c.id "

		+" WHERE"
			+" c.deleted = 0 ");

		sql.append(getSearchCriteriaString(args));

		sql.append(" ORDER BY ( 0+0 ");

		if (args != null) {
			if (args.containsKey("_surname"))
				sql.append(" + IF( c.surname = :_surname,1,0) + IF( c.surname LIKE '"+args.get("_surname")+"%',1,0) ");
			if (args.containsKey("_firstName"))
			sql.append(" + IF( c.firstName = :_firstName,1,0) + IF( c.firstName LIKE '"+args.get("_firstName")+"%',1,0) ");
			if (args.containsKey("_nameCh"))
			sql.append(" + IF( c.nameCh = :_nameCh,1,0) + IF( c.nameCh LIKE '"+args.get("_nameCh")+"%',1,0) ");
		}
		sql.append(" ) DESC, ");
		sql.append(" TRIM(c.firstName), TRIM(c.surname), TRIM(c.nameCh), c.id ");

		if (args != null) {
			if (args.containsKey("start") && args.containsKey("limit"))
				sql.append(" LIMIT :start, :limit ");
		}

		return jdbcDao.queryForList(sql.toString(), args);
	}

	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
	public Map<String, Object> countSearch(Map<String, Object> args) {

		SecurityUtils.authArgs(args);

		StringBuilder sql = new StringBuilder(
			" SELECT "
			+" COUNT(*) AS count "
		+" FROM"
		+" ( SELECT "
		+" c.id "
	+" FROM "
		+" customer c "
		+" 	LEFT JOIN "
		
		+" (SELECT "
		+" o.custId, SUM(oi.price) AS amount, "
		+" SUM(IF(o.status = "+Order.STATUS_ON_HAND+", 1, 0)) AS onHandOrderNum "
		+" FROM"
		+" 	orders o "
		+" LEFT JOIN order_item oi ON oi.orderId = o.id "
		+" WHERE "
		+" 	o.deleted = 0 AND oi.deleted = 0"
		+" 		AND o.status != - 1"
		+" GROUP BY o.custId) custOrders ON custOrders.custId = c.id"

		+" 	LEFT JOIN"
		+" (SELECT "
		+" 	o.custId, SUM(p.amount) AS amount"
		+" FROM"
		+" 	orders o"
		+" LEFT JOIN payment p ON p.orderId = o.id"
		+" WHERE"
		+" 	o.deleted = 0 AND p.deleted = 0 "
		//+" AND o.ignoreC = 0 "
		+" 		AND o.status != - 1"
		+" GROUP BY o.custId) custPayment ON custPayment.custId = c.id"

	+" WHERE"
		+" c.deleted = 0");

		sql.append(getSearchCriteriaString(args));

		sql.append(") _t");

		return jdbcDao.queryForMap(sql.toString(), args);
	}

	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
	public Map<String, Object> queryForMap(Integer id) {
		Map<String,Object> args = MapUtils.toHashMap(Params.ID, id);
		SecurityUtils.authArgs(args);

		String sql = "SELECT c.*, "
			+" CONCAT_WS(' ',c.firstName , c.surname , c.nameCh) AS customerName, "
			+" CONCAT_WS('/',c.phone1, c.phone2) AS phoneNum "
			+" FROM customer c WHERE c.id = :id ";

		if(args.containsKey("sysGroupId")){
			sql+=" AND c.sysGroupId = :sysGroupId ";
		}
		return jdbcDao.queryForMap(sql, args);
	}

	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
	public Map<String, Object> getOrderIds(Map<String, Object> args) {

		SecurityUtils.authArgs(args);

		String sql = "SELECT GROUP_CONCAT(o.id ORDER BY o.date DESC, o.id DESC) AS ids "
		+" FROM orders o "
		+" WHERE 1=1 "
		+" AND o.custId = :custId ";
		
		if (args != null) {
			if (args.containsKey("ignoreOrderId"))
				sql+=" AND o.id != :ignoreOrderId ";

			if(args.containsKey("sysGroupId")){
				sql+=" AND o.sysGroupId = :sysGroupId ";
			}
		}
		sql+=" GROUP BY o.custId ";

		return jdbcDao.queryForMap(sql, args);
	}

	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
	public List<Map<String, Object>> getRelatedOrder(int id) {

		Map<String,Object> args = MapUtils.toHashMap(Params.ID, id);
		SecurityUtils.authArgs(args);

		String sql = " SELECT o.* FROM orders o WHERE o.custId = :id ";
		if (args != null) {
			
			if(args.containsKey("sysGroupId")){
				sql+=" AND o.sysGroupId = :sysGroupId ";
			}
		}
		return jdbcDao.queryForList(sql, args);
	}
}
