package com.ozs.appshow.service.impl;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.HtmlUtils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ozs.appshow.dto.ChartInfoDTO;
import com.ozs.appshow.dto.ChartTypeDTO;
import com.ozs.appshow.dto.GetChartViewDTO;
import com.ozs.appshow.dto.RichTextFileR;
import com.ozs.appshow.dto.mdm.KpiListColumnDTO;
import com.ozs.appshow.dto.mdm.MdmCreateTableDTO;
import com.ozs.appshow.dto.mdm.MdmInfoColumn;
import com.ozs.appshow.dto.mdm.MdmInfoDatabase;
import com.ozs.appshow.dto.mdm.MdmInfoTable;
import com.ozs.appshow.mapper.ChartInfoMapper;
import com.ozs.appshow.service.ChartInfoService;
import com.ozs.appshow.util.FileTool;
import com.ozs.appshow.util.MdmApi;
import com.ozs.appshow.util.R;

import jodd.util.ArraysUtil;

@Service
public class ChartInfoServiceImpl implements ChartInfoService {

	@Autowired
	private ChartInfoMapper chartInfoMapper;
	@Autowired
	private MdmApi mdmApi;

	@Override
	public R<List<ChartInfoDTO>> listChartInfo(String chartName, Integer chartType, Integer page, Integer pageSize,
			String sort, String order) throws Exception {
		R<ChartInfoDTO> r = new R<>();
		r.pageStart(sort, order, page, pageSize);
		List<ChartInfoDTO> result = chartInfoMapper.listChartInfo(chartName, chartType);
		return r.page(result);
	}

	@Override
	public R<List<MdmInfoDatabase>> listDatabase(String resourceName) {
		return new R<>(mdmApi.listDatabase(resourceName));
	}

	@Override
	public R<List<MdmInfoTable>> listTableByDatabaseId(String databaseId) {
		return new R<>(mdmApi.listTableByDatabaseId(databaseId));
	}

	@Override
	public R<List<MdmInfoColumn>> listColumnByTableId(String tableId) {
		return new R<>(mdmApi.listColumnByTableId(tableId));
	}

	@Override
	public R<List<ChartTypeDTO>> listChartType() {
		return new R<>(chartInfoMapper.listChartType());
	}

	@Override
	public R<String> getDML(String tableId, String columnIds) {
		StringBuffer sb = new StringBuffer();
		List<KpiListColumnDTO> columns = null;
		sb.append("SELECT ");
		if (columnIds != null && !"".equals(columnIds)) {
			columns = mdmApi.listColumnByIds(Arrays.asList(columnIds.split(",")));
		}
		// 获取字段信息
		// List<MdmInfoColumn> columns = mdmApi.listColumnByTableId(tableId);
		MdmCreateTableDTO tableDto = mdmApi.getTableInfoById(tableId).getData();
		if (columns != null && columns.size() != 0) {
			for (KpiListColumnDTO column : columns) {
				sb.append(column.getColumnName()).append(",");
			}
		}
		sb.deleteCharAt(sb.length() - 1);
		sb.append(" FROM " + tableDto.getTableName());
		R<String> r = new R<>();
		r.setData(sb.toString());
		return r;
	}

	@Override
	public R<ChartInfoDTO> getChartInfo(String id) {
		ChartInfoDTO chartInfoDTO = chartInfoMapper.getChartInfo(id);
		// 富文本类型
		if (chartInfoDTO.getChartType() != null && chartInfoDTO.getChartType() == 4) {
			// 解析富文本特殊字符
			chartInfoDTO.setRichText(HtmlUtils.htmlUnescape(chartInfoDTO.getRichText()));
		}
		R<ChartInfoDTO> r = new R<>();
		r.setData(chartInfoDTO);
		return r;
	}

	// 文件导入路径
	@Value("${import.file.path}")
	protected String importPath;
	// 文件导入文件夹

	@Value("${import.file.url}")
	protected String fileUrl;

	/**
	 * 文件名入库成功后缀_succ
	 */
	protected final static String FILE_SUCC = "_succ";

	@Override
	public R<String> loadChartFile(MultipartFile multipartFile, String userId) throws Exception {
		String fileName = multipartFile.getOriginalFilename();
		// 获取文件后缀
		StringBuffer fileSb = new StringBuffer(fileName.substring(fileName.indexOf("."), fileName.length()));
		// 重命名上传文件
		fileSb.insert(fileSb.indexOf("."), System.currentTimeMillis());
		FileTool.uploadFile(multipartFile.getBytes(), importPath + userId + File.separator, fileSb.toString());
		R<String> r = new R<>();
		r.setMsg("上传成功");
		r.setData(fileUrl + userId + File.separator + fileSb.toString());
		return r;
	}

	/**
	 * 检查文件，文件重命名加_succ后缀，标示入库成功。并删除没有成功入库的文件
	 * 
	 * @return
	 */
	private String checkFile(String filePath, String userId) {
		StringBuffer fpsb = new StringBuffer(filePath);
		// 获取文件存储的绝对路径
		File file = new File(filePath.replaceAll(fileUrl, importPath));
		// 重命名文件，加上_succ 入库成功后缀
		fpsb.insert(fpsb.lastIndexOf("."), FILE_SUCC);
		file.renameTo(new File(fpsb.toString().replaceAll(fileUrl, importPath)));
		// 删除用户文件夹中没有_succ后缀的文件
		File dir = new File(importPath + userId + File.separator);
		File[] files = dir.listFiles(); // 该文件目录下文件全部放入数组
		// 删除没有_succ后缀文件
		for (File f : files) {
			if (f.getName().indexOf(FILE_SUCC) == -1) {
				f.delete();
			}
		}
		return fpsb.toString();
	}

	/**
	 * 保存图表
	 */
	@Transactional
	@Override
	public R<String> saveChartInfo(ChartInfoDTO chartInfoDTO) throws Exception {
		R<String> r = new R<>();
		// 判断图表名称重复问题
		ChartInfoDTO chartInfo = chartInfoMapper.getChartInfoByName(chartInfoDTO.getChartName());
		if (chartInfo != null) {
			r.setCode("-1");
			r.setMsg("图表名称已存在");
			r.setData("图表名称已存在");
			return r;
		}
		if (chartInfoDTO.getChartType() != null) {
			// 富文本类型
			if (chartInfoDTO.getChartType() == 4) {
				// 富文本文件上传检查
				chartInfoDTO.setRichText(checkRichTextFile(chartInfoDTO.getRichText(), chartInfoDTO.getUpdateUserId()));
				// 将富文本文档所有特殊字符转义为对应的数字以十六进制格式引用，用作保存
				chartInfoDTO.setRichText(HtmlUtils.htmlEscapeHex(chartInfoDTO.getRichText()));
			}
			// 图片和视频类型
			if (chartInfoDTO.getChartType() == 5 || chartInfoDTO.getChartType() == 6) {
				// 文件检查，文件重命名加_succ后缀，标示入库成功
				chartInfoDTO.setFilePath(checkFile(chartInfoDTO.getFilePath(), chartInfoDTO.getUpdateUserId()));
			}
		}
		chartInfoMapper.saveChartInfo(chartInfoDTO);
		r.setMsg("保存成功");
		r.setData("保存成功");
		return r;

	}

	/**
	 * 修改图表
	 */
	@Transactional
	@Override
	public R<String> updateChartInfo(ChartInfoDTO chartInfoDTO) {
		R<String> r = new R<>();
		// 判断图表名称重复问题
		ChartInfoDTO chartInfo = chartInfoMapper.getChartInfoByName(chartInfoDTO.getChartName());
		if (chartInfo != null && !chartInfo.getId().equals(chartInfoDTO.getId())) {
			r.setCode("-1");
			r.setMsg("图表名称已存在");
			r.setData("图表名称已存在");
			return r;
		}
		if (chartInfoDTO.getChartType() != null) {
			// 富文本类型
			if (chartInfoDTO.getChartType() == 4) {
				// 修改前图表信息
				ChartInfoDTO chart = chartInfoMapper.getChartInfo(chartInfoDTO.getId());
				// 解译富文本
				chart.setRichText(HtmlUtils.htmlUnescape(chart.getRichText()));
				// 清楚修改后富文本中不存在的_succ后缀文件
				if (chart.getRichText() != null && !"".equals(chart.getRichText())) {
					cleanRichTextFile(chart.getRichText(), chartInfoDTO.getRichText());
				}
				// 富文本文件上传检查
				chartInfoDTO.setRichText(checkRichTextFile(chartInfoDTO.getRichText(), chartInfoDTO.getUpdateUserId()));
				// 将富文本文档所有特殊字符转义为对应的数字以十六进制格式引用，用作保存
				chartInfoDTO.setRichText(HtmlUtils.htmlEscapeHex(chartInfoDTO.getRichText()));
				// 图片或是视频类型，需要判断文件是否重新上传
			} else if (chartInfoDTO.getChartType() == 5 || chartInfoDTO.getChartType() == 6) {
				// 判断保存文件是否重新上传
				if (chartInfoDTO.getFilePath().indexOf(FILE_SUCC) == -1) {
					// 文件检查，文件重命名加_succ后缀，标示入库成功
					chartInfoDTO.setFilePath(checkFile(chartInfoDTO.getFilePath(), chartInfoDTO.getUpdateUserId()));
					// 删除原文件
					ChartInfoDTO chart = chartInfoMapper.getChartInfo(chartInfoDTO.getId());
					File df = new File(chart.getFilePath().replaceAll(fileUrl, importPath));
					df.delete();
				}
			}
		}
		chartInfoMapper.updateChartInfo(chartInfoDTO);
		r.setMsg("修改成功");
		r.setData("修改成功");
		return r;
	}

	/**
	 * 清楚修改后富文本中不存在的_succ后缀文件
	 * 
	 * @param befText
	 *            修改前文本
	 * @param aftText
	 *            修改后文本
	 */
	private void cleanRichTextFile(String befText, String aftText) {
		List<String> befFile = new ArrayList<>();
		List<String> aftFile = new ArrayList<>();
		String[] befArr = befText.split("import/");
		String[] aftArr = aftText.split("import/");
		// 修改前没有使用上传文件
		if (befArr.length <= 1) {
			return;
		}
		// 获取修改前文本中文件
		for (String befStr : befArr) {
			if (befStr.contains(FILE_SUCC)) {
				// 判断是否是视频格式，视频格式
				if (befStr.contains(".mp4")) {
					befFile.add(befStr.substring(0, befStr.indexOf(".mp4") + 4));
				} else {
					befFile.add(befStr.substring(0, befStr.indexOf("\"")));
				}
			}
		}
		// 获取修改后文本中文件
		for (String aftStr : aftArr) {
			if (aftStr.contains(FILE_SUCC)) {
				// 判断是否是视频格式，视频格式
				if (aftFile.contains(".mp4")) {
					aftFile.add(aftStr.substring(0, aftStr.indexOf(".mp4") + 4));
				} else {
					aftFile.add(aftStr.substring(0, aftStr.indexOf("\"")));
				}
			}
		}
		for (String filePath : befFile) {
			// 判断修改后不存在的文件，进行删除
			if (!ArraysUtil.contains(aftFile.toArray(), filePath)) {
				new File(importPath + filePath).delete();
			}
		}
	}

	/**
	 * 图表编辑
	 */
	@Transactional
	@Override
	public R<String> updateChartJson(ChartInfoDTO chartInfoDTO) throws Exception {
		R<String> r = new R<>();
		chartInfoMapper.updateChartJson(chartInfoDTO);
		r.setMsg("编辑成功");
		r.setData("编辑成功");
		return r;
	}

	@Transactional
	@Override
	public R<String> removeChart(String id) {
		R<String> r = new R<>();
		// 判断报表是否在使用
		Integer count = chartInfoMapper.countRelSubjectChart(id);
		if (count != null && count != 0) {
			r.setCode("-1");
			r.setMsg("图表正在使用，不能删除");
			r.setData("删除失败");
			return r;
		}
		// 删除原文件
		ChartInfoDTO chart = chartInfoMapper.getChartInfo(id);
		File df = new File(chart.getFilePath().replaceAll(fileUrl, importPath));
		df.delete();
		chartInfoMapper.removeChart(id);
		r.setMsg("删除成功");
		r.setData("删除成功");
		return r;
	}

	@Override
	public R<List<ChartInfoDTO>> listChartInfoByClass(String classCode) throws Exception {
		R<List<ChartInfoDTO>> r = new R<>();
		List<ChartInfoDTO> result = chartInfoMapper.listChartInfoByClass(classCode);
		r.setData(result);
		return r;
	}

	/**
	 * 图表预览
	 */
	@Override
	public R<GetChartViewDTO> getChartView(String id) {
		R<GetChartViewDTO> r = new R<>();
		GetChartViewDTO chartViewDTO = new GetChartViewDTO();
		ChartInfoDTO chart = chartInfoMapper.getChartInfo(id);
		// 数据表格类型
		if (chart.getChartType() == 1) {
			// 获取数据
			Map<String, String> sqlMap = new HashMap<>();
			sqlMap.put("sql", chart.getDML());
			List<Map<String, Object>> data = mdmApi.listData(chart.getDatabaseId(), sqlMap);
			// 获取表格表头
			List<String> header = getHeaderByDML(chart.getDML());
			chartViewDTO.setHeader(header);
			// 数据获取返回null,返回查询数据异常
			if (data == null) {
				r.setCode("-1");
				r.setMsg("表格数据获取异常！请检查DML~");
				return r;
			}
			chartViewDTO.setData(data);
			// 富文本类型
		} else if (chart.getChartType() != null && chart.getChartType() == 4) {
			// 解析富文本特殊字符
			chart.setRichText(HtmlUtils.htmlUnescape(chart.getRichText()));
		} else if (chart.getChartType() == 2) {
			// 柱折图
			chartViewDTO.setChartJson(dataToChartJson(chart));
		} else if (chart.getChartType() == 3) {
			// 饼图
			chartViewDTO.setChartJson(dataToChartJsonPie(chart));
		} else if (chart.getChartType() == 7 || chart.getChartType() == 8) {
			// 地图类型和条形图 TODO
			chartViewDTO.setChartJson(chart.getChartJson());
		}
		chartViewDTO.setFilePath(chart.getFilePath());
		// chartViewDTO.setChartJson(chart.getChartJson());
		chartViewDTO.setChartType(chart.getChartType());
		chartViewDTO.setChartName(chart.getChartName());
		chartViewDTO.setRichText(chart.getRichText());
		r.setData(chartViewDTO);
		return r;
	}

	/**
	 *
	 * @param chart
	 * @return
	 */
	private String dataToChartJson(ChartInfoDTO chart) {
		// 获取数据
		Map<String, String> sqlMap = new HashMap<>();
		sqlMap.put("sql", chart.getDML());
		// 执行DML获取数据
		List<Map<String, Object>> data = mdmApi.listData(chart.getDatabaseId(), sqlMap);
		JSONObject json = JSON.parseObject(chart.getChartJson());
		// 获取并设置X轴对象
		if (data != null && data.get(0).containsKey("xaxis")) {
			JSONArray xAxisArr = (JSONArray) json.get("xAxis");
			JSONObject xAxis = xAxisArr.getJSONObject(0);
			List<String> xDataList = new ArrayList<>();
			for (Map<String, Object> o : data) {
				xDataList.add((String) o.get("xaxis"));
			}
			// 使用set集合去重
			HashSet<String> h = new HashSet<>(xDataList);
			xDataList.clear();
			xDataList.addAll(h);
			xAxis.put("data", xDataList);
			// JSONObject xData = (JSONObject) xAxis.get("data");
			json.put("xAxis", xAxis);
		}
		// 设置option中series数据元素
		JSONArray series = (JSONArray) json.get("series");
		for (Object s : series) {
			String name = null;
			List<String> dataList = new ArrayList<>();
			JSONObject sj = (JSONObject) s;
			name = sj.get("name").toString();
			for (Map<String, Object> m : data) {
				if (m.get(name) != null) {
					// TODO 判断是否是数字
					dataList.add(m.get(name).toString());
				}
			}
			sj.put("data", dataList);
		}
		json.put("series", series);
		return json.toJSONString();
	}

	/**
	 * 饼图数据获取和返回json
	 * 
	 * @param chart
	 * @return
	 */
	private String dataToChartJsonPie(ChartInfoDTO chart) {
		// 获取数据
		Map<String, String> sqlMap = new HashMap<>();
		sqlMap.put("sql", chart.getDML());
		// 执行DML获取数据
		List<Map<String, Object>> data = mdmApi.listData(chart.getDatabaseId(), sqlMap);
		JSONObject json = JSON.parseObject(chart.getChartJson());
		// 设置option中series数据元素
		JSONObject series = (JSONObject) json.get("series");

		// DML数据查询条数不唯一，不可以做饼图返回值--展示示例图
		if (data == null || data.size() != 1) {
			series.put("name", "DML数据查询条数不唯一，不可以做饼图返回值--展示示例图");
			json.put("series", series);
			return json.toJSONString();
		}

		// 设置option中data数据元素
		Map<String, Object> pieData = data.get(0);
		List<Map<String, Object>> dataArr = new ArrayList<>();
		for (String keyName : pieData.keySet()) {
			Map<String, Object> dataM = new HashMap<>();
			dataM.put("name", keyName);
			dataM.put("value", pieData.get(keyName));
			dataArr.add(dataM);
		}
		series.put("data", dataArr);
		json.put("series", series);
		return json.toJSONString();
	}

	/**
	 * 根据DML语句获取查询语句表头,有别名查询表头为别名，没有别名为字段名称
	 * 
	 * @param dml
	 * @return
	 */
	private List<String> getHeaderByDML(String dml) {
		List<String> header = new ArrayList<>();
		// 转小写
		dml = dml.toLowerCase();
		// 去除所有换行符
		dml = dml.replace("\t", "");
		dml = dml.replace("`", "");
		// 将空格和别名关键词as替换为特殊符号 &
		dml = dml.replace(" as ", "&");
		dml = dml.replace(" ", "&");
		for (String str : Arrays.asList(dml.substring(dml.indexOf("select") + 6, dml.indexOf("from")).split(","))) {
			String[] arr = str.split("&");
			for (int i = arr.length - 1; i >= 0; i--) {
				if (!arr[i].equals(null) && !arr[i].equals("")) {
					header.add(arr[i]);
					break;
				}
			}
		}
		return header;
	}

	@Override
	public RichTextFileR uploadRichTextFile(MultipartFile multipartFile, String userId) throws Exception {
		String fileName = multipartFile.getOriginalFilename();
		// 获取文件后缀
		StringBuffer fileSb = new StringBuffer(fileName.substring(fileName.indexOf("."), fileName.length()));
		// 重命名上传文件
		fileSb.insert(fileSb.indexOf("."), System.currentTimeMillis());
		FileTool.uploadFile(multipartFile.getBytes(), importPath + userId + File.separator, fileSb.toString());
		RichTextFileR r = new RichTextFileR();
		r.setError(0);
		r.setUrl(fileUrl + userId + File.separator + fileSb.toString());
		return r;
	}

	/**
	 * 富文本上传文件，检查用户文件夹，文件重命名加_succ后缀，标示入库成功。并删除没有成功入库的文件
	 * 
	 * @return
	 */
	private String checkRichTextFile(String richText, String userId) {
		StringBuffer fpsb = null;
		// 获取文件存储的绝对路径
		String path = importPath + userId + File.separator;
		File dir = new File(path);
		File[] files = dir.listFiles(); // 该文件目录下文件全部放入数组
		// 删除没有_succ后缀文件
		for (File f : files) {
			String name = f.getName();
			// 判断没有_succ后缀的文件
			if (f.getName().indexOf(FILE_SUCC) == -1) {
				if (!richText.contains(name)) {
					// 删除没有在富文本中包含的文件
					f.delete();
				} else {
					// 在富文本中使用的文件,重命名文件加_succ后缀
					fpsb = new StringBuffer(name);
					// 重命名文件，加上_succ 入库成功后缀
					fpsb.insert(fpsb.lastIndexOf("."), FILE_SUCC);
					f.renameTo(new File(path + fpsb));
					// 替换富文本中文件名称。加_succ后缀
					richText = richText.replaceAll(name, fpsb.toString());
				}
			}
		}
		return richText;
	}

}
