/**
 * 
 */
package com.abm.mainet.brms.rest.water.service.impl;

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

import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import com.abm.mainet.brms.core.constant.Constant;
import com.abm.mainet.brms.core.dto.LookUp;
import com.abm.mainet.brms.core.dto.WSRequestDTO;
import com.abm.mainet.brms.core.dto.WSResponseDTO;
import com.abm.mainet.brms.core.utility.CommonMasterUtility;
import com.abm.mainet.brms.core.utility.RuleEngineClient;
import com.abm.mainet.brms.entity.Organisation;
import com.abm.mainet.brms.entity.TaxMasEntity;
import com.abm.mainet.brms.entity.TbTaxDetMasEntity;
import com.abm.mainet.brms.rest.service.ServiceMasterService;
import com.abm.mainet.brms.rest.service.TaxMasterService;
import com.abm.mainet.brms.rest.water.service.WaterService;
import com.abm.mainet.rule.water.datamodel.Consumption;
import com.abm.mainet.rule.water.datamodel.NoOfDays;
import com.abm.mainet.rule.water.datamodel.WaterRateMaster;
import com.abm.mainet.rule.water.datamodel.WaterTaxCalculation;

/**
 * @author Vivek.Kumar
 *
 */
@Service
public class WaterServiceImpl implements WaterService{

	
	@Resource
	private ServiceMasterService serviceMasterService;
	
	@Resource
	private TaxMasterService taxMasService; 
	
	private static final String		SERVICE_ID_CANT_BE_ZERO	= "ServiceCode cannot be null or empty";
	private static final String		ORG_ID_CANT_BE_ZERO		= "orgId cannot be zero(0)";
	private static final String		CHARGE_APPLICABLE_AT_CANT_BE_ZERO	= "chargeApplicableAt cannot be empty or zero(0)";
	private static final String		CHARGE_APPLICABLE_AT_MUST_BE_NUMERIC	= "chargeApplicableAt must be numeric";
	private static final String		DATAMODEL_FIELD_CANT_BE_NULL	= "dataModel field within WSRequestDTO dto cannot be null";
	private static final String		NO_CHARGE_FOUND	= "No Charge found for inserted model";
	private static final String		DATAMODEL_MUST_NOT_CONTAIN_EMPTY_LIST	= "WsRequestDTO's dataModel field must not contain an empty list";
	private static final String		NO_WATER_CONSUMPTION_FOUND	= "No water consumption found for provided input model";
	private static final String		NO_OF_DAYS_NOT_FOUND	= "NoOfDays not found for provided input model";
	private static final String		NO_WATER_RATE_FOUND	= "No Water Rate found for provided input model";
	private static final String		NO_WATER_TAX_FOUND	= "No Water Tax found for provided input model";
	
	private static final Logger LOGGER = LoggerFactory.getLogger(WaterServiceImpl.class);
	
	private static Map<Long,String> dependsOnFactorMap = null;
	
	@Override
	public ResponseEntity<?> listOfApplicableServiceCharge (WSRequestDTO requestDTO) throws CloneNotSupportedException {
		
		ResponseEntity<?> responseEntity = null;
		if (requestDTO.getDataModel() == null) {
			responseEntity = ResponseEntity.status(HttpStatus.BAD_REQUEST).body(DATAMODEL_FIELD_CANT_BE_NULL);
		} else {
			WaterRateMaster waterRateMaster = (WaterRateMaster) CommonMasterUtility.castRequestToDataModel(requestDTO, WaterRateMaster.class);
			responseEntity = validateDataModel(waterRateMaster);
			if (responseEntity.getStatusCode() == HttpStatus.OK) {
				responseEntity = populateOtherFieldsForServiceCharge(waterRateMaster);
			}
			
		}
		
		return responseEntity;
	}
	/**
	 * validating WaterRateMaster model
	 * @param waterRateMaster
	 * @return
	 */
	private ResponseEntity<?> validateDataModel (WaterRateMaster waterRateMaster) {
		StringBuilder builder = new StringBuilder();
		if (waterRateMaster.getServiceCode() == null ||
				waterRateMaster.getServiceCode().isEmpty()) {
			builder.append(SERVICE_ID_CANT_BE_ZERO).append(",");
		}
		if (waterRateMaster.getOrgId() == 0l) {
			builder.append(ORG_ID_CANT_BE_ZERO).append(",");
		}
		if (waterRateMaster.getChargeApplicableAt() == null || waterRateMaster.getChargeApplicableAt().isEmpty()) {
			builder.append(CHARGE_APPLICABLE_AT_CANT_BE_ZERO).append(",");
		} else if (!StringUtils.isNumeric(waterRateMaster.getChargeApplicableAt())) {
			builder.append(CHARGE_APPLICABLE_AT_MUST_BE_NUMERIC);
		}
		return builder.toString().isEmpty()
				? ResponseEntity.status(HttpStatus.OK).body("")
				: ResponseEntity.status(HttpStatus.BAD_REQUEST).body(builder.toString());
	}

	
	@Override
	public ResponseEntity<?> populateOtherFieldsForServiceCharge (WaterRateMaster waterRateMaster) throws CloneNotSupportedException {

		ResponseEntity<?> responseEntity = null;
		List<WaterRateMaster> listOfCharges;
		if (serviceMasterService.isChargeApplicable(
				waterRateMaster.getServiceCode(), waterRateMaster.getOrgId())) {
			List<TaxMasEntity> applicableCharges = taxMasService.fetchAllApplicableServiceCharge(
					serviceMasterService.getServiceIdByKey(waterRateMaster.getServiceCode(), waterRateMaster.getOrgId()),
					waterRateMaster.getOrgId(), 
					Long.parseLong(waterRateMaster.getChargeApplicableAt()));
			Organisation organisation = new Organisation();
			organisation.setOrgid(waterRateMaster.getOrgId());
			listOfCharges = settingAllFields(applicableCharges, waterRateMaster, organisation);
			WSResponseDTO responseDTO = new WSResponseDTO();
			responseDTO.setResponseObj(listOfCharges);
			responseDTO.setWsStatus(Constant.Status.SUCCESS);
			responseEntity = ResponseEntity.status(HttpStatus.OK).body(responseDTO);
		} else {
			WSResponseDTO responseDTO = new WSResponseDTO();
			responseDTO.setFree(true);
			responseDTO.setWsStatus(Constant.Status.SUCCESS);
			responseEntity = ResponseEntity.status(HttpStatus.OK).body(responseDTO);
		}

		return responseEntity;
	}
	
	
	
	/**
	 * 
	 * @param applicableCharges
	 * @param rateMaster
	 * @return
	 * @throws CloneNotSupportedException 
	 */
	private List<WaterRateMaster> settingAllFields(List<TaxMasEntity> applicableCharges, WaterRateMaster rateMaster, Organisation organisation) throws CloneNotSupportedException {
		
		
		List<WaterRateMaster> list = new ArrayList<>();
		
		for (TaxMasEntity entity : applicableCharges) {
			WaterRateMaster waterRateMaster = (WaterRateMaster) rateMaster.clone();
				// SLD for dependsOnFactor
				String taxType = CommonMasterUtility.findLookUpDesc(Constant.Prefix.FSD, rateMaster.getOrgId(), Long.parseLong(entity.getTaxMethod()));
				String chargeApplicableAt = CommonMasterUtility.findLookUpDesc(Constant.Prefix.CAA, entity.getOrgid(), entity.getTaxApplicable());
				
				waterRateMaster.setTaxType(taxType);
				waterRateMaster.setTaxCode(entity.getTaxCode());
				waterRateMaster.setChargeApplicableAt(chargeApplicableAt);
				waterRateMaster.setChargeDescEng(entity.getTaxDesc());
				waterRateMaster = settingTaxCategories(waterRateMaster, entity, organisation);
				waterRateMaster.setDependsOnFactorList(settingDependsOnFactor(entity.getListOfTbTaxDetMas(),organisation));
				//waterRateMaster.setDeptId(entity.getDepartment().getDpDeptid());
				waterRateMaster.setTaxId(entity.getTaxId());
				
				list.add(waterRateMaster);
		}
		
		return list;
	} 
	
	/**
	 * not being used as per requirement changed,
	 * might be used later
	 * @param orgId
	 * @return
	 */
	@SuppressWarnings("unused")
	private String findTaxGroupId(Organisation orgId) {
		
		List<LookUp> lookUps = CommonMasterUtility.getListLookup("TAG", orgId);
		String taxGroup = "";
		if (lookUps == null || lookUps.isEmpty()) {
			throw new NullPointerException("LookUps not found for Prefix TAG");
		}
		for (LookUp lookUp : lookUps) {
			if ("ST".equalsIgnoreCase(lookUp.getLookUpCode())) {
				taxGroup = Long.toString(lookUp.getLookUpId());
			}
		}
		
		return taxGroup;
	}
	
	private List<String> settingDependsOnFactor(List<TbTaxDetMasEntity> taxDetList,Organisation orgId) {
		
		if (dependsOnFactorMap == null) {
			cacheDependsOnFactors(orgId);
		}
		List<String> dependsOnFactorList = new ArrayList<>();
		
		if (taxDetList != null) {
			for (TbTaxDetMasEntity entity : taxDetList) {
				dependsOnFactorList.add(dependsOnFactorMap.get(entity.getTdDependFact()));
			}
		}
		
		return dependsOnFactorList;
	}
	
	/**
	 * cache Depends on factor only once
	 * @param orgId
	 */
	private static void cacheDependsOnFactors(Organisation orgId) {
		
		List<LookUp> lookUps = CommonMasterUtility.getListLookup(Constant.Prefix.SLD, orgId);
		dependsOnFactorMap = new HashMap<>();
		for (LookUp lookUp : lookUps) {
			dependsOnFactorMap.put(lookUp.getLookUpId(), lookUp.getLookUpCode());
		}
		
	}
	
	
	/**
	 * 
	 * @param waterRateMaster
	 * @param enity
	 * @param organisation
	 * @return
	 */
	private WaterRateMaster settingTaxCategories(WaterRateMaster waterRateMaster, TaxMasEntity enity, Organisation organisation) {
		
		List<LookUp> taxCategories = CommonMasterUtility.getLevelData(Constant.Prefix.TAC, Constant.LEVEL_ONE, organisation);
		for (LookUp lookUp : taxCategories) {
			if (enity.getTaxCategory1() != null) {
				if (lookUp.getLookUpId() == enity.getTaxCategory1()) {
					waterRateMaster.setTaxCategory(lookUp.getDescLangFirst());
					break;
				 }
			}
			 
		}
		List<LookUp> taxSubCategories = CommonMasterUtility.getLevelData(Constant.Prefix.TAC, Constant.LEVEL_TWO, organisation);
		for (LookUp lookUp : taxSubCategories) {
			if (enity.getTaxCategory2() != null) {
				if (lookUp.getLookUpId() == enity.getTaxCategory2()) {
					waterRateMaster.setTaxSubCategory(lookUp.getLookUpCode());
					break;
				 }
			}
			 
		}
		return waterRateMaster;
		
	}

	@Override
	public ResponseEntity<?> calculateServiceCharges (WSRequestDTO requestDTO, WSResponseDTO responseDTO) {
		
		if (requestDTO.getDataModel() == null) {
			return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(DATAMODEL_FIELD_CANT_BE_NULL);
		}
		return findApplicableCharges(requestDTO);
	}

	@Override
	public ResponseEntity<?> findApplicableCharges (WSRequestDTO requestDTO) {

		
		List<WaterRateMaster> waterRates = new ArrayList<>();
		WSResponseDTO responseDTO = new WSResponseDTO();
		ResponseEntity<?> responseEntity = null;
		List<Object> list = CommonMasterUtility.castRequestToDataModels(requestDTO,WaterRateMaster.class);
		if (list != null && !list.isEmpty()) {
			for (Object model : list) {
				WaterRateMaster ruleResult = RuleEngineClient.findWaterRateMaster((WaterRateMaster) model);
				if (ruleResult != null) {
					waterRates.add(ruleResult);
					responseDTO.setWsStatus(Constant.Status.SUCCESS);
				} else {
					LOGGER.error(NO_CHARGE_FOUND+model);
					throw new NullPointerException(NO_CHARGE_FOUND);
				}
			}
			responseDTO.setResponseObj(waterRates);
			responseEntity = ResponseEntity.status(HttpStatus.OK).body(responseDTO);
		} else {
			throw new NullPointerException(DATAMODEL_MUST_NOT_CONTAIN_EMPTY_LIST);
		}
		
		return responseEntity;
	}
	
	
	
	@Override
	public ResponseEntity<?> findWaterConsumption(WSRequestDTO requestDTO) {
		
		if (requestDTO.getDataModel() == null) {
			return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(DATAMODEL_FIELD_CANT_BE_NULL);
		} else {
			Consumption consumption = (Consumption) CommonMasterUtility.castRequestToDataModel(requestDTO,Consumption.class);
			String ruleResult = RuleEngineClient.findWaterConsumption(consumption);
			if (ruleResult.equals(Constant.StatusCode.INTERNAL_SERVER_ERROR)) {
				return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(NO_WATER_CONSUMPTION_FOUND);
			} else {
				WSResponseDTO responseDTO = new WSResponseDTO();
				responseDTO.setResponseObj(Double.parseDouble(ruleResult));
				responseDTO.setWsStatus(Constant.Status.SUCCESS);
				return ResponseEntity.status(HttpStatus.OK).body(responseDTO);
			}
		}
	}
	
	
	@Override
	public ResponseEntity<?> findNoOfDays(WSRequestDTO requestDTO) {
		
		if (requestDTO.getDataModel() == null) {
			return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(DATAMODEL_FIELD_CANT_BE_NULL);
		} else {
			NoOfDays noOfDays = (NoOfDays) CommonMasterUtility.castRequestToDataModel(requestDTO,NoOfDays.class);
				String ruleResult = RuleEngineClient.findNoOfDays(noOfDays);
				if (ruleResult.equals(Constant.StatusCode.INTERNAL_SERVER_ERROR)) {
					return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(NO_OF_DAYS_NOT_FOUND);
				} else {
					WSResponseDTO responseDTO = new WSResponseDTO();
					responseDTO.setResponseObj(Long.parseLong(ruleResult));
					responseDTO.setWsStatus(Constant.Status.SUCCESS);
					return ResponseEntity.status(HttpStatus.OK).body(responseDTO);
				}
		}
		
	}

	@Override
	public ResponseEntity<?> findWaterRate(WSRequestDTO requestDTO) {
		
		if (requestDTO.getDataModel() == null) {
			return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(DATAMODEL_FIELD_CANT_BE_NULL);
		} else {
			WaterRateMaster rateMaster = (WaterRateMaster) CommonMasterUtility.castRequestToDataModel(requestDTO,WaterRateMaster.class);
			WaterRateMaster ruleResult = RuleEngineClient.findWaterRateMaster(rateMaster);
			if (ruleResult == null) {
				return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(NO_WATER_RATE_FOUND);
			} else {
				WSResponseDTO responseDTO = new WSResponseDTO();
				responseDTO.setResponseObj(ruleResult);
				responseDTO.setWsStatus(Constant.Status.SUCCESS);
				return ResponseEntity.status(HttpStatus.OK).body(responseDTO);
			}
		}
	}
	
	@Override
	public ResponseEntity<?> findWaterTax(WSRequestDTO requestDTO) {
		
		
		ResponseEntity<?> responseEntity = null;
		if (requestDTO.getDataModel() == null) {
			return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(DATAMODEL_FIELD_CANT_BE_NULL);
		} else {
			WaterTaxCalculation taxCalculation = (WaterTaxCalculation) CommonMasterUtility.castRequestToDataModel(requestDTO,WaterTaxCalculation.class);
			String ruleResult = RuleEngineClient.findWaterTaxMaster(taxCalculation);
			if (ruleResult.equals(Constant.StatusCode.INTERNAL_SERVER_ERROR)) {
				return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(NO_WATER_TAX_FOUND);
			} else {
				WSResponseDTO responseDTO = new WSResponseDTO();
				responseDTO.setResponseObj(Double.parseDouble(ruleResult));
				responseDTO.setWsStatus(Constant.Status.SUCCESS);
				responseEntity =ResponseEntity.status(HttpStatus.OK).body(responseDTO);
			}
		
		}
		
		return responseEntity;
	
	}
	
	@Override
	public ResponseEntity<?> findWaterConsumptionAndDays(WSRequestDTO requestDTO) {
		
		ResponseEntity<?> responseEntity = null;
		if (requestDTO.getDataModel() == null) {
			return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(DATAMODEL_FIELD_CANT_BE_NULL);
		} else {
			List<Object> responseList = new ArrayList<>();
			Consumption consumption  = (Consumption) CommonMasterUtility.castRequestToDataModelByPosition(requestDTO, Consumption.class, Constant.INDEX_ZERO);
			responseEntity = findConsumption(consumption, responseList);
			if (responseEntity.getStatusCode() == HttpStatus.OK) {
				NoOfDays noOfDays  = (NoOfDays) CommonMasterUtility.castRequestToDataModelByPosition(requestDTO, NoOfDays.class, Constant.INDEX_ONE);
				responseEntity = findNoOfDays(noOfDays, responseList);
			}
		}

		return responseEntity;
	}
	
	
	private ResponseEntity<?> findNoOfDays(NoOfDays noOfDays,List<Object> responseList) {
		
		String ruleResult = RuleEngineClient.findNoOfDays(noOfDays);
		if (ruleResult.equals(Constant.StatusCode.INTERNAL_SERVER_ERROR)) {
			return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(NO_OF_DAYS_NOT_FOUND);
		} else {
			responseList.add(Long.parseLong(ruleResult));
			WSResponseDTO responseDTO = new WSResponseDTO();
			responseDTO.setResponseObj(responseList);
			responseDTO.setWsStatus(Constant.Status.SUCCESS);
			return ResponseEntity.status(HttpStatus.OK).body(responseDTO);
		}
		
	}
	
	private ResponseEntity<?> findConsumption(Consumption consumption,List<Object> responseList) {
		
		String ruleResult = RuleEngineClient.findWaterConsumption(consumption);
		if (ruleResult.equals(Constant.StatusCode.INTERNAL_SERVER_ERROR)) {
			return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(NO_OF_DAYS_NOT_FOUND);
		} else {
			responseList.add(Double.parseDouble(ruleResult));
			WSResponseDTO responseDTO = new WSResponseDTO();
			responseDTO.setResponseObj(responseList);
			responseDTO.setWsStatus(Constant.Status.SUCCESS);
			return ResponseEntity.status(HttpStatus.OK).body(responseDTO);
		}
		
	}
	
	
	
}
