package com.uas.report.service.impl;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URLEncoder;
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 javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSONObject;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.PdfCopy;
import com.lowagie.text.pdf.PdfReader;
import com.uas.report.SpecialProperties;
import com.uas.report.core.exception.ReportException;
import com.uas.report.schedule.model.TaskInformation;
import com.uas.report.schedule.service.Executable;
import com.uas.report.schedule.service.TaskService;
import com.uas.report.service.FileService;
import com.uas.report.util.FileUtils;
import com.uas.report.util.ReportConstants;
import com.uas.report.util.ReportUtils;
import com.uas.report.util.ZipUtils;

@Service
public class FileServiceImpl implements FileService {

	@Autowired
	private SpecialProperties specialProperties;

	@Autowired
	private TaskService taskService;

	/**
	 * 第一次执行的延迟时间间隔为30秒
	 */
	private static final long INITIAL_DELAY = 30 * 1000;

	/**
	 * 产生的pdf的有效期（最高为10分钟）
	 */
	private static final int PDF_MAX_INVALID_INTERVAL = 10 * 60 * 1000;

	private Logger logger = LoggerFactory.getLogger(getClass());

	/**
	 * 查看、压缩文件时，对文件进行过滤
	 */
	public static final FileFilter fileFilter = new FileFilter() {
		@Override
		public boolean accept(File file) {
			if (file == null || !file.exists()) {
				return false;
			}
			// 不支持查看、压缩：jasper文件、tmp路径
			String filePath = file.getPath();
			if (filePath.endsWith(".jasper") || filePath.endsWith("tmp") || filePath.contains("\\tmp\\")
					|| filePath.contains("/tmp/")) {
				return false;
			}
			return true;
		}
	};

	@Override
	public String autoDeploy(String sourceUserName, String destinationUserNames) {
		if (StringUtils.isEmpty(sourceUserName) || StringUtils.isEmpty(destinationUserNames)) {
			throw new ReportException("参数不能为空：sourceUserName，destinationUserNames");
		}
		try {
			byte[] data = null;
			String stantardJrxmlsUrl = String.format(specialProperties.getStandardJrxmlsUrl(), sourceUserName);
			// 如果本机提供标准模板下载，直接从本地获取数据
			if (specialProperties.hasStandardJrxmls()) {
				data = getStandardJrxmls(sourceUserName);
			}
			// 本机没有标准模板，则先下载标准模板数据
			else {
				logger.info("get standardJrxmls from " + stantardJrxmlsUrl + "...");
				JSONObject jsonObject = JSONObject.parseObject(IOUtils.toString(HttpClients.createDefault()
						.execute(new HttpGet(URI.create(stantardJrxmlsUrl))).getEntity().getContent()));
				data = jsonObject.getBytes("data");
			}
			if (ArrayUtils.isEmpty(data)) {
				throw new ReportException("标准模板不存在");
			}
			String[] userNameArray = destinationUserNames.split(",");
			// 创建账套路径并解压模板zip包
			for (String userName : userNameArray) {
				ZipUtils.unzip(data, getMasterPath(userName));
			}
			return "已自动部署：" + destinationUserNames;
		} catch (UnsupportedOperationException | IOException e) {
			throw new ReportException(e).setDetailedMessage(e);
		}
	}

	@Override
	public String upload(final String userName, String fileType, final MultipartFile file) {
		if (StringUtils.isEmpty(userName)) {
			return "未传入当前账套名称！";
		}
		if (file == null) {
			return "文件为空，无法进行上传！";
		}
		if (StringUtils.isEmpty(fileType)) {
			fileType = "jrxml";
		}

		// 如果上传的是模板zip包，直接获取字节数据，将其解压到相应的账套下
		if (fileType.equals("zip")) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						ZipUtils.unzip(file.getBytes(), getMasterPath(userName));
					} catch (IOException e) {
						throw new ReportException("文件解压失败").setDetailedMessage(e);
					}
				}
			}).start();
			return "文件上传成功";
		}

		String fileName = file.getOriginalFilename();
		StringBuilder stringBuilder = new StringBuilder(specialProperties.getLocalBaseDir()).append("/");
		// jrxml模板和图片分别放在jrxml和Picture文件夹下，其他资源放在当前账套根路径下
		if (fileType.equals("jrxml")) {
			stringBuilder.append(userName).append("/").append("jrxml").append("/");
		} else if (fileType.equals("image")) {
			stringBuilder.append(userName).append("/").append("Picture").append("/");
		} else if (fileType.equals("other")) {
			stringBuilder.append(userName).append("/");
		} else {
			throw new ReportException("不支持上传该类型的文件：" + fileType);
		}

		stringBuilder.append(fileName);
		String targetFilePath = stringBuilder.toString();
		final File targetFile = new File(targetFilePath);
		if (!targetFile.getParentFile().exists()) {
			targetFile.getParentFile().mkdirs();
		}
		try {
			FileUtils.write(targetFile.getAbsolutePath(), file.getBytes());
			return "成功上传文件至：" + targetFile.getPath();
		} catch (IllegalStateException | IOException | ReportException e) {
			logger.error("", e);
			return "文件上传失败： " + fileName;
		}
	}

	@Override
	public String upload(String filePath, Boolean isAbsolutePath, MultipartFile[] files) {
		if (ArrayUtils.isEmpty(files)) {
			return "文件为空，无法进行上传！";
		}
		filePath = getAbsolutePath(filePath, isAbsolutePath);
		File targetFile = new File(filePath + "/" + files[0].getOriginalFilename());
		// 检查路径是否存在
		if (!targetFile.getParentFile().exists()) {
			targetFile.getParentFile().mkdirs();
		}

		StringBuilder stringBuilder = new StringBuilder();
		for (MultipartFile file : files) {
			targetFile = new File(targetFile.getParent() + "/" + file.getOriginalFilename());
			try {
				// 不能使用file.transferTo(targetFile)，
				// 因为在spring boot下上传文件时会对临时路径进行处理，
				// 导致最终文件路径不正确，如果手动设置临时路径为根路径，
				// 又会因为权限问题导致文件写入失败，
				// 所以自己在指定路径创建文件，而不使用transferTo方法
				FileUtils.write(targetFile.getAbsolutePath(), file.getBytes());
				stringBuilder.append("成功上传文件至：");
			} catch (IllegalStateException | IOException | ReportException e) {
				logger.error("", e);
				stringBuilder.append("上传文件失败：");
			}
			stringBuilder.append(targetFile.getPath()).append("\n");
		}
		return stringBuilder.toString();
	}

	@Override
	public String getJrxmlFilePath(String userName, String reportName) {
		ReportUtils.checkParameters(userName, reportName);
		return new StringBuilder(getMasterPath(userName)).append(specialProperties.getLocalJrxmlDir()).append("/")
				.append(reportName).append(".jrxml").toString();
	}

	@Override
	public String getMasterPath(String userName) {
		if (StringUtils.isEmpty(userName)) {
			throw new ReportException("参数不能为空：userName");
		}
		return new StringBuilder(specialProperties.getLocalBaseDir()).append("/").append(userName).toString();
	}

	@Override
	public byte[] getStandardJrxmls(String userName) {
		if (StringUtils.isEmpty(userName)) {
			throw new ReportException("未传入当前账套名称！");
		}
		if (!specialProperties.hasStandardJrxmls()) {
			throw new ReportException("没有" + userName + "标准模板！");
		}
		byte[] data = ZipUtils.zipFolder(getMasterPath(specialProperties.getStandardMaster()) + "/" + userName,
				FileServiceImpl.fileFilter);
		if (ArrayUtils.isEmpty(data)) {
			throw new ReportException("压缩失败");
		}
		return data;
	}

	@Override
	public void downloadStandardJrxmls(String userName, HttpServletResponse response) {
		if (StringUtils.isEmpty(userName)) {
			throw new ReportException("未传入当前账套名称！");
		}
		if (!specialProperties.hasStandardJrxmls()) {
			throw new ReportException("没有" + userName + "标准模板！");
		}
		downloadZip(specialProperties.getStandardMaster() + "/" + userName, response);
	}

	@Override
	public void downloadZip(String userName, HttpServletResponse response) {
		if (StringUtils.isEmpty(userName)) {
			throw new ReportException("未传入当前账套名称！");
		}
		String masterPath = getMasterPath(userName);
		byte[] data = ZipUtils.zipFolder(masterPath, FileServiceImpl.fileFilter);
		if (ArrayUtils.isEmpty(data)) {
			throw new ReportException("压缩失败");
		}
		download(data, new File(masterPath).getName() + ".zip", response);
	}

	@Override
	public void download(String filePath, Boolean isAbsolutePath, HttpServletResponse response) {
		if (StringUtils.isEmpty(filePath) || response == null) {
			throw new ReportException("参数不能为空：filePath，response");
		}
		filePath = getAbsolutePath(filePath, isAbsolutePath);
		File file = new File(filePath);
		if (!file.exists()) {
			throw new ReportException("文件不存在：" + filePath);
		}

		byte[] data = null;
		String fileName = "";
		// 下载文件夹之前，需进行压缩
		if (file.isDirectory()) {
			fileName = file.getName() + ".zip";
			data = ZipUtils.zipFolder(filePath, fileFilter);
		} else {
			fileName = file.getName();
			try {
				InputStream inputStream = new FileInputStream(file);
				data = new byte[inputStream.available()];
				inputStream.read(data);
				inputStream.close();
			} catch (IOException e) {
				logger.error("", e);
			}
		}
		if (ArrayUtils.isEmpty(data)) {
			throw new ReportException("下载失败：" + filePath);
		}
		download(data, fileName, response);
	}

	@Override
	public void download(byte[] data, String fileName, HttpServletResponse response) {
		if (ArrayUtils.isEmpty(data) || StringUtils.isEmpty(fileName) || response == null) {
			throw new ReportException("参数不能为空：data，filePath，response");
		}
		try {
			response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
			OutputStream outputStream = response.getOutputStream();
			outputStream.write(data);
			outputStream.flush();
			outputStream.close();
		} catch (IOException e) {
			logger.error("", e);
		}
	}

	@Override
	public String delete(String filePath, Boolean isAbsolutePath) {
		// 路径不能为空，不能只包含'/'（根路径），不能含有'..'（不允许通过该方式删除上一级）
		if (StringUtils.isEmpty(filePath) || StringUtils.isEmpty(filePath.replaceAll("/", ""))
				|| filePath.contains("..")) {
			throw new ReportException("路径不合法：" + filePath);
		}
		filePath = getAbsolutePath(filePath, isAbsolutePath);
		if (StringUtils.isEmpty(filePath)) {
			throw new ReportException("参数不能为空：filePath");
		}
		File file = new File(filePath);
		if (!file.exists()) {
			throw new ReportException("文件不存在，不必删除：" + filePath);
		}
		FileUtils.deleteDir(file);
		return filePath;
	}

	@Override
	public List<Map<String, Object>> listFiles(String filePath, Boolean isAbsolutePath) {
		filePath = getAbsolutePath(filePath, isAbsolutePath);
		File file = new File(filePath);
		if (!file.exists()) {
			throw new ReportException("文件不存在：" + filePath);
		}

		List<Map<String, Object>> result = new ArrayList<>();
		// 如果是文件，直接获取文件信息
		if (file.isFile()) {
			result.add(getFileInformation(file, isAbsolutePath));
		} else {
			File[] files = file.listFiles(fileFilter);
			// 文件夹放在前面展示
			List<File> directoryList = new ArrayList<>();
			List<File> fileList = new ArrayList<>();
			for (File f : files) {
				if (f.isDirectory()) {
					directoryList.add(f);
				} else {
					fileList.add(f);
				}
			}
			result.addAll(getFileInformations(directoryList, isAbsolutePath));
			result.addAll(getFileInformations(fileList, isAbsolutePath));
		}
		return result;
	}

	/**
	 * 获取多个文件的信息
	 * 
	 * @param files
	 *            文件
	 * @param isAbsolutePath
	 *            文件路径是否为绝对路径
	 * @return 文件信息，包括name(String)、lastModified(String)、size(Long)、filePath(
	 *         String，可能是相对路径，也可能是绝对路径，具体取决于isAbsolutePath)、isDirectory(Boolean)
	 */
	private List<Map<String, Object>> getFileInformations(List<File> files, Boolean isAbsolutePath) {
		List<Map<String, Object>> informationList = new ArrayList<>();
		for (File file : files) {
			informationList.add(getFileInformation(file, isAbsolutePath));
		}
		return informationList;
	}

	/**
	 * 获取文件信息
	 * 
	 * @param file
	 *            文件
	 * @param isAbsolutePath
	 *            文件路径是否为绝对路径
	 * @return 文件信息，包括name(String)、lastModified(String)、size(Long)、filePath(
	 *         String，可能是相对路径，也可能是绝对路径，具体取决于isAbsolutePath)、isDirectory(Boolean)
	 */
	private Map<String, Object> getFileInformation(File file, Boolean isAbsolutePath) {
		if (file == null || !file.exists()) {
			return null;
		}
		Map<String, Object> information = new HashMap<>();
		information.put("name", file.getName());
		if (isAbsolutePath == null || !isAbsolutePath) {
			information.put("filePath", getRelativePath(file));
		} else {
			information.put("filePath", file.getPath());
		}
		information.put("lastModified",
				new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(file.lastModified())));
		if (file.isFile()) {
			information.put("size", file.length());
			information.put("isDirectory", false);
		} else {
			information.put("isDirectory", true);
		}
		return information;
	}

	/**
	 * 获取文件相对于本地资源根路径的路径
	 * 
	 * @param file
	 *            文件
	 * @return 相对于本地资源根路径的路径
	 */
	private String getRelativePath(File file) {
		if (file == null || !file.exists()) {
			return null;
		}
		// 获取相对路径，须将本地资源根路径替换掉，并且文件分隔符统一使用 '/'
		return file.getPath().replace(new File(specialProperties.getLocalBaseDir()).getPath(), "").replace("\\", "/");
	}

	@Override
	public boolean isFileValid(String fileAbsolutePath, String jrxmlFileAbsolutePath) {
		if (!StringUtils.isEmpty(fileAbsolutePath) && !StringUtils.isEmpty(jrxmlFileAbsolutePath)) {
			File file = new File(fileAbsolutePath);
			File jrxmlFile = new File(jrxmlFileAbsolutePath);
			if (file.exists() && jrxmlFile.exists()) {
				long interval = new Date().getTime() - file.lastModified();
				long validity = PDF_MAX_INVALID_INTERVAL - interval;
				// 有效期大于0并且比模板文件新
				if (validity > 0 && file.lastModified() > jrxmlFile.lastModified()) {
					logger.info(file.getName() + " will be expired after " + validity / 1000.0 + "s");
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public String generateFileName(String userName, String profile, String reportName, String whereCondition,
			String otherParameters, String exportFileType) {
		if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(reportName)) {
			return null;
		}
		StringBuilder stringBuilder = new StringBuilder(userName);
		if (!StringUtils.isEmpty(profile)) {
			stringBuilder.append(profile);
		}
		if (!StringUtils.isEmpty(whereCondition)) {
			stringBuilder.append(whereCondition);
		}
		if (!StringUtils.isEmpty(otherParameters)) {
			stringBuilder.append(otherParameters);
		}
		if (!StringUtils.isEmpty(exportFileType)) {
			stringBuilder.append(exportFileType);
		}
		// 文件名：reportName + hashCode
		return reportName + "_" + stringBuilder.toString().hashCode();
	}

	@Override
	public int getPageSize(String pdfFileAbsolutePath) {
		try {
			return new PdfReader(pdfFileAbsolutePath).getNumberOfPages();
		} catch (IOException e) {
			throw new ReportException(e).setDetailedMessage(e);
		}
	}

	@Override
	public void createPagedPdfFiles(String pdfFileAbsolutePath) {
		try {
			PdfReader pdfReader = new PdfReader(pdfFileAbsolutePath);
			for (int i = 1; i <= pdfReader.getNumberOfPages(); i++) {
				Document document = new Document();
				FileOutputStream fileOutputStream = new FileOutputStream(
						pdfFileAbsolutePath.replace(".pdf", "_" + i + ".pdf"));
				PdfCopy pdfCopy = new PdfCopy(document, fileOutputStream);
				document.open();
				// 原pdf文件的每一页生成新的pdf
				pdfCopy.addPage(pdfCopy.getImportedPage(pdfReader, i));
				pdfCopy.close();
				// 不关闭，生成的pdf无法正常打开
				document.close();
				fileOutputStream.close();
			}
			pdfReader.close();
		} catch (IOException | DocumentException e) {
			throw new ReportException(e).setDetailedMessage(e);
		}
	}

	/**
	 * 根据指定路径获取绝对路径，如果isAbsolutePath为真，直接返回指定路径，否则拼接上模板根路径
	 * 
	 * @param filePath
	 *            指定路径
	 * @param isAbsolutePath
	 *            是否为绝对路径
	 * @return 绝对路径
	 */
	private String getAbsolutePath(String filePath, Boolean isAbsolutePath) {
		if (StringUtils.isEmpty(filePath)) {
			throw new ReportException("路径不合法：" + filePath);
		}
		// 不是绝对路径的话，则相对于模板根路径
		if (isAbsolutePath == null || !isAbsolutePath) {
			filePath = specialProperties.getLocalBaseDir() + "/" + filePath;
		}
		return filePath;
	}

	@Override
	public TaskInformation newDeleteGeneratedFilesTask(Integer taskPeriod) {
		if (taskPeriod == null) {
			throw new NullPointerException();
		}
		String title = "定时删除pdf等文件";
		Executable command = new Executable() {
			@Override
			public String execute() {
				FileUtils.deleteDir(new File(ReportConstants.GENERATED_FILES_ABSOLUTE_PATH), new FileFilter() {
					@Override
					public boolean accept(File file) {
						// 只删除已过期的文件
						if (new Date().getTime() - file.lastModified() - PDF_MAX_INVALID_INTERVAL > 0) {
							return true;
						}
						return false;
					}
				});
				return "success";
			}
		};
		TaskInformation taskInformation = new TaskInformation(title, command, INITIAL_DELAY, taskPeriod);
		taskService.newTask(taskInformation);
		return taskInformation;
	}

}
