/*******************************************************************************
 * 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.order.service;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.ffii.core.dao.JdbcDao;
import com.ffii.core.utils.MapUtils;
import com.ffii.core.utils.SecurityUtils;
import com.ffii.core.web.AbstractService;
import com.ffii.tbms.file.FileBlob;
import com.ffii.tbms.user.service.SysGroupService;

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

import freemarker.template.utility.SecurityUtilities;
import net.sf.jasperreports.engine.JREmptyDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.JasperRunManager;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.export.JRPdfExporter;
import net.sf.jasperreports.engine.type.WhenNoDataTypeEnum;
import net.sf.jasperreports.export.SimpleExporterInput;
import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput;


@Service
public class InvoiceService extends AbstractService {

	@Autowired
	private JdbcDao jdbcDao;

	@Autowired
	private SysGroupService sysGroupService;
	
	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public Map<String, Object> search(int orderId) {
		StringBuilder sql = new StringBuilder(
			" SELECT o.id, "
			+" o.date As orderDate, "
			+" concat(o.type,o.code) As orderNo, "
			+" concat_ws(' ', c.firstName, c.surname, c.nameCh) AS custName, "
			+" concat_ws(' / ', c.phone1, c.phone2) AS phone, "
			+" c.firstName, "
			+" c.surname, "
			+" c.nameCh, "
			+" c.phone1, "
			+" c.phone2, "
			+" c.email "
			+" FROM orders o "
			+" LEFT JOIN customer c ON c.id = o.custId "
			+" WHERE o.id = "+orderId
			);

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

	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public List<Map<String, Object>> getOrderItems(int orderId) {
		StringBuilder sql = new StringBuilder(
			" SELECT  "
			+ " name AS description, "
			+ " price AS amount "
			+ " FROM "
			+ " order_item oi "
			+ " WHERE "
			+ " oi.deleted = 0 AND oi.orderId = "+orderId
			);

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

	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public Map<String, Object> getOrderItemTotalAmount(int orderId) {
		StringBuilder sql = new StringBuilder(
			" SELECT  "
			+ " sum(oi.price) As totalAmount "
			+ " FROM "
			+ " order_item oi "
			+ " WHERE "
			+ " oi.deleted = 0 AND oi.orderId = "+orderId
			);

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

	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public Map<String, Object> getPaymentTotal(int orderId) {
		StringBuilder sql = new StringBuilder(
			" select sum(p.amount) As totalPayment from payment p where p.deleted = 0 and p.orderId = "+orderId);
		return jdbcDao.queryForMap(sql.toString(), null);
	}

	public Map<String, Object> getInvoicePdfParams(int orderId){
		Map<String, Object> params = search(orderId);

			FileBlob blob = sysGroupService.getIconBlob();
			Map<String, Object> sysGroup = sysGroupService.find(SecurityUtils.getUser().getSysGroupId());

			params.put("companyIcon", blob==null?null:blob.getBytes());
			params.put("companyName", sysGroup== null?"": sysGroup.get("companyName"));

			String contactStr = "";
			if(sysGroup != null){
				int count = 0;
				if(sysGroup.get("address")!=null && !sysGroup.get("address").toString().isBlank()){
					contactStr = sysGroup.get("address").toString();
					count++;
				}
				if(sysGroup.get("phone")!=null && !sysGroup.get("phone").toString().isBlank()){
					contactStr += (count%2==1?"<br/>":"")+ "Tel:"+sysGroup.get("phone").toString();
					count++;
				}
				if(sysGroup.get("fax")!=null && !sysGroup.get("fax").toString().isBlank()){
					contactStr += (count%2==1?"<br/>":"")+(count > 0 && count%2==0?"&nbsp;&nbsp;&nbsp;":"")+"Fax:"+sysGroup.get("fax").toString();
					count++;
				}
				if(sysGroup.get("email")!=null && !sysGroup.get("email").toString().isBlank()){
					contactStr += (count%2==1?"<br/>":"")+(count > 0 && count%2==0?"&nbsp;&nbsp;&nbsp;":"")+"Email:"+sysGroup.get("email").toString();
				}
			}

			params.put("contactStr", contactStr);

			BigDecimal total = (BigDecimal) getOrderItemTotalAmount(orderId).get("totalAmount");
			BigDecimal payment = (BigDecimal)getPaymentTotal(orderId).get("totalPayment");
			BigDecimal deposit = total;
			if(payment != null)
				deposit = total.subtract(payment);

			params.put("payment", payment==null?BigDecimal.ZERO: payment);
			params.put("total", total == null?BigDecimal.ZERO: total);
			params.put("deposit", deposit== null? BigDecimal.ZERO:deposit);

			SimpleDateFormat sdf = new SimpleDateFormat("MMMMM d, yyyy");
			if(params.get("orderDate") != null)
				params.put("date", sdf.format(params.get("orderDate")));
			else
				params.put("date", new Date());

			params.put("format", "pdf");

			return params;
	}


	public byte[] genInvoice(int orderId) throws IOException, JRException, IllegalStateException {

		Map<String, Object> params = getInvoicePdfParams(orderId);

		List<Map<String,Object>> list = getOrderItems(orderId);
		if(list == null || list.size()==0){
			for(int i =0; i<12; i++)
				list.add(new HashMap<String, Object>());
		}else{
			int line = 12-list.size();
			for(int i = 0; i<line; i++)
				list.add(new HashMap<String, Object>());
		}

		InputStream templateInputStream = this.getServletContext().getResourceAsStream("WEB-INF/report/invoiceTemp.jrxml");
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();

		JasperReport jasperReport = JasperCompileManager.compileReport(templateInputStream);
		jasperReport.setWhenNoDataType(WhenNoDataTypeEnum.ALL_SECTIONS_NO_DETAIL);
		JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, params, new JRBeanCollectionDataSource(list));

		JRPdfExporter exporter = new JRPdfExporter();
		exporter.setExporterInput(new SimpleExporterInput(jasperPrint));
		exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(byteOut));
		exporter.exportReport();

		return byteOut.toByteArray();

	}

	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
	public void updateTemplateBody(String body){
		/*
		Path path = Paths.get(this.getServletContext().getRealPath("WEB-INF/mail/invoiceTemplateMail.ftl"));
		Charset charset = StandardCharsets.UTF_8;
		String content = new String(Files.readAllBytes(path), charset);
		String oldBody = getTemplateBody(content);
		content = content.replace(oldBody, body);
		
		Files.write(path, content.getBytes(charset));
		*/

		jdbcDao.executeUpdate(
			" UPDATE sys_groups sg SET mailBody = :mailBody WHERE id = :id ", 
			MapUtils.toHashMap(
				"mailBody", body, 
				"id", SecurityUtils.getUser().getSysGroupId()
			));

	}

	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
	public void updateTemplateSubject(String subject){
		jdbcDao.executeUpdate(
			" UPDATE sys_groups sg SET mailSubject = :mailSubject WHERE id = :id ", 
			MapUtils.toHashMap(
				"mailSubject", subject, 
				"id", SecurityUtils.getUser().getSysGroupId()
			));
		//jdbcDao.executeUpdate(" UPDATE settings SET value = :value WHERE name = 'MAIL.temp.subject' ", MapUtils.toHashMap("value", subject));
	}


	public String getTemplateBody(String content) throws IOException{
		logger.info("content : "+content);
		logger.info("index: "+content.indexOf("<body>"));
		logger.info("content : "+content.substring(content.indexOf("<body>")));
		logger.info("index-last : "+content.indexOf("</body>"));
		return content.substring(content.indexOf("<body>")+"<body>".length(), content.indexOf("</body>"));
	}
/*
	public String getTemplateBody() throws IOException{
		Path path = Paths.get(this.getServletContext().getRealPath("WEB-INF/mail/invoiceTemplateMail.ftl"));
		Charset charset = StandardCharsets.UTF_8;
		String content = new String(Files.readAllBytes(path), charset);
		return getTemplateBody(content);
	}
	*/

	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public String getTemplateBody() throws IOException{
		 
		Map<String, Object> body = jdbcDao.queryForMap(
			"SELECT mailBody FROM sys_groups WHERE id = :id ",
			 MapUtils.toHashMap("id", SecurityUtils.getUser().getSysGroupId())
		);

		return body==null || body.get("mailBody") == null?"":body.get("mailBody").toString();
	}

	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public String getTemplateSubject() throws IOException{
		 
		Map<String,Object> subject = jdbcDao.queryForMap(
			"SELECT mailSubject FROM sys_groups WHERE id = :id ",
			 MapUtils.toHashMap("id", SecurityUtils.getUser().getSysGroupId())
		);
		return subject==null||subject.get("mailSubject")== null? "":subject.get("mailSubject").toString();
	}

}
