package incheon.ags.por.web;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.egovframe.rte.fdl.cryptography.EgovCryptoService;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import incheon.ags.mrb.main.util.EditorHtmlSanitizerUtil;
import incheon.ags.por.service.PorBoardFileService;
import incheon.ags.por.service.PorBoardService;
import incheon.ags.por.util.PorFileUtils;
import incheon.ags.por.vo.PorBoardFileVO;
import incheon.ags.por.vo.PorBoardVO;
import incheon.ags.por.vo.PorFileVO;
import incheon.com.cmm.EgovWebUtil;
import incheon.com.cmm.api.DefaultApiResponse;
import incheon.com.cmm.service.EgovFileMngService;
import incheon.com.cmm.service.FileVO;
import lombok.RequiredArgsConstructor;

/**
 * @ClassName : PorController.java
 * @Description : 공간정보포털 컨트롤러
 *
 * @author : 관리자
 * @since : 2025. 07. 17
 * @version : 1.0
 *
 *          <pre>
 * << 개정이력(Modification Information) >>
 *
 *   수정일              수정자               수정내용
 *  -------------  ------------   ---------------------
 *   2023. 10. 10    관리자               최초 생성
 *   2024. 12. 19    관리자               패널 기반 플랫폼으로 전면 개편
 *   2025. 07. 17    관리자               공간정보포털로 전환
 *          </pre>
 *
 */
@Controller
@RequestMapping("/ags/por")
@RequiredArgsConstructor
public class PorBoardController {
	
	private final PorBoardService porBoardService;
	private final PorBoardFileService porBoardFileService;
	
	private final PorFileUtils porFileUtils;
	
	@Resource(name = "egovARIACryptoService")
    private final EgovCryptoService cryptoService;
	
	@Resource(name = "EgovFileMngService")
	private EgovFileMngService fileService;
	
	/**
     * 통합포탈 진입페이지
     * @param model
     * @return JSP 페이지
     * @exception Exception
     */
    @GetMapping("/view.do")
    public String viewMap(ModelMap model) throws Exception {
        return "redirect:notice/list.do";
    }
    
    /**
     * 게시판 작성
     * @param model
     * @return 결과
     * @exception Exception
     */
    @PostMapping(path = "/board/insert.do", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public ResponseEntity<DefaultApiResponse<Map<String, Object>>> insert(
    		@ModelAttribute PorBoardVO porBoardVO
    		, MultipartHttpServletRequest multiRequest
    		, ModelMap model
	) throws Exception {
    	Map<String, Object> resultMap = new HashMap<String, Object>();
    	
    	porBoardVO.setPrvtYn(porBoardVO.getPrvtYn() == null ? false : true);
    	// pstCn: Base64 디코딩 후 HTML 정화 (MRB 방식, ModSecurity XSS 오탐 회피)
    	decodeAndSanitizePstCn(porBoardVO);
    	
    	long result = porBoardService.insertBoard(porBoardVO);
    	
		if(result > 0) {
			if(porBoardVO.getAttachments() != null) {
				// 첨부파일 추가
				List<PorFileVO> fileList = porFileUtils.storeFiles(porBoardVO.getAttachments());
				if(fileList != null) {
					fileList.forEach(file -> {
						try {
							if (!fileList.isEmpty()) {
								PorBoardFileVO boardFileVO = new PorBoardFileVO();
								boardFileVO.setBbsUnqKey(porBoardVO.getUnqKey());
								boardFileVO.setFileUnqKey(file.getUnqKey());
								System.out.println("fileUnqKey : " + file.getUnqKey());
								porBoardFileService.insertBoardFile(boardFileVO);
							}
						} catch (Exception e) {
							// e.printStackTrace();
							resultMap.put("code", e.getLocalizedMessage());
						}
					});
				}
				if(resultMap.get("code") != null) {
					return ResponseEntity.badRequest()
		                    .body(DefaultApiResponse.error(400, "파일저장오류", String.valueOf(resultMap.get("code"))));
				}
			}
		}
    	
    	return ResponseEntity.ok(DefaultApiResponse.success(resultMap));
    }
    
    /**
     * 게시판 수정
     * @param model
     * @return 결과
     * @exception Exception
     */
    @PostMapping(path = "/board/update.do", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public ResponseEntity<DefaultApiResponse<Map<String, Object>>> update(
    		@ModelAttribute PorBoardVO porBoardVO
    		, MultipartHttpServletRequest multiRequest
    		, ModelMap model
	) throws Exception {
    	Map<String, Object> resultMap = new HashMap<String, Object>();
    	
    	porBoardVO.setPrvtYn(porBoardVO.getPrvtYn() == null ? false : true);
    	// pstCn: Base64 디코딩 후 HTML 정화 (MRB 방식, ModSecurity XSS 오탐 회피)
    	decodeAndSanitizePstCn(porBoardVO);
    	// 신규파일 추가
    	List<MultipartFile> newFileList = porBoardVO.getNewFileList();
    	
    	if(newFileList != null) {
    		List<PorFileVO> fileList = porFileUtils.storeFiles(newFileList);
			fileList.forEach(file -> {
				try {
					if (!fileList.isEmpty()) {
						PorBoardFileVO boardFileVO = new PorBoardFileVO();
						boardFileVO.setBbsUnqKey(porBoardVO.getUnqKey());
						boardFileVO.setFileUnqKey(file.getUnqKey());
						porBoardFileService.insertBoardFile(boardFileVO);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			});
		}
    	
    	
    	// 기존파일 제거
		List<Integer> removeFileList = porBoardVO.getRemoveFileList();
		if(removeFileList != null) {
			removeFileList.forEach(fileUnqKey -> {
				PorBoardFileVO vo = new PorBoardFileVO();
				vo.setFileUnqKey(fileUnqKey);
				System.out.println(vo);
				try {
					List<PorFileVO> removeList = porBoardFileService.selectBoardFileList(vo);
					if(removeList != null && removeList.size() > 0) {
		    			// 물리적 파일 제거 로직 생기면 여기에 추가
		    			porBoardFileService.deleteBoardFile(vo);
		    			removeList.forEach(file -> {
		    				try {
		    					PorBoardFileVO tempVO = new PorBoardFileVO();
		    					tempVO.setFileUnqKey(file.getUnqKey());
		    					
		    					int fileResult = porFileUtils.deleteFiles(file);
		    					if(fileResult > 0) {
		    						porBoardFileService.deleteBoardFile(tempVO);
		    					}
		    					
		    				} catch (Exception e) {
		    					e.printStackTrace();
		    				}
		    			});
		    		}
				} catch (Exception e) {
					e.printStackTrace();
				}
			});
		}
    	
    	int result = porBoardService.updateBoard(porBoardVO);
    	
    	resultMap.put("result", result);
    	
    	return ResponseEntity.ok(DefaultApiResponse.success(resultMap));
    }
    
    /**
     * 게시판 삭제
     * @param model
     * @return JSP 페이지
     * @exception Exception
     */
    @DeleteMapping("/board/delete.do")
    public ResponseEntity<DefaultApiResponse<Map<String, Object>>> delete(
    		@RequestParam int unqKey
    		, ModelMap model
    		) throws Exception {
    	Map<String, Object> resultMap = new HashMap<String, Object>();
    	
		PorBoardVO porBoardVO = new PorBoardVO();
		porBoardVO.setUnqKey(unqKey);
		
		int result = porBoardService.deleteBoard(porBoardVO);
		
		if(result > 0) {
			// 첨부파일 제거 / 이긴한데 공통쪽 물리적 파일 제거 로직은 없어서...정책 체크하고 진행
			// 파일리스트 호출
			PorBoardFileVO vo = new PorBoardFileVO();
			vo.setBbsUnqKey(unqKey);
			List<PorFileVO> fileList = porBoardFileService.selectBoardFileList(vo);
			if(fileList != null && fileList.size() > 0) {
				// 물리적 파일 제거 로직 생기면 여기에 추가
				porBoardFileService.deleteBoardFile(vo);
				fileList.forEach(file -> {
					try {
						PorBoardFileVO tempVO = new PorBoardFileVO();
						tempVO.setFileUnqKey(file.getUnqKey());
						
						int fileResult = porFileUtils.deleteFiles(file);
						if(fileResult > 0) {
							porBoardFileService.deleteBoardFile(tempVO);
						}
						
					} catch (Exception e) {
						e.printStackTrace();
					}
				});
			}
		}
		
		resultMap.put("result", result);
    	
    	return ResponseEntity.ok(DefaultApiResponse.success(resultMap));
    }
    
	/**
	 * Summernote 에디터 이미지 업로드 (에디터 전용 경로에 저장)
	 */
	@PostMapping(path = "/board/uploadImage.do", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
	public ResponseEntity<DefaultApiResponse<Map<String, Object>>> uploadImage(
			@RequestParam("file") MultipartFile file) throws Exception {
		Map<String, Object> data = new HashMap<>();
		if (file == null || file.isEmpty()) {
			return ResponseEntity.badRequest()
					.body(DefaultApiResponse.error(400, "파일이 없습니다.", "EMPTY_FILE"));
		}
		PorFileVO vo = porFileUtils.storeEditorImage(file);
		if (vo == null) {
			return ResponseEntity.badRequest()
					.body(DefaultApiResponse.error(500, "파일 저장에 실패했습니다.", "STORE_FAILED"));
		}
		String url = "/sgp/por/file.do?unqKey=" + vo.getUnqKey() + "&atchFileSn=" + vo.getAtchFileSn();
		data.put("url", url);
		data.put("fileId", vo.getUnqKey());
		return ResponseEntity.ok(DefaultApiResponse.success(data));
	}

	/**
	 * Summernote 에디터 이미지 삭제 (취소 시 업로드된 이미지 정리용)
	 */
	@DeleteMapping("/board/deleteImage.do")
	public ResponseEntity<DefaultApiResponse<Map<String, Object>>> deleteImage(
			@RequestParam("fileId") int fileId) throws Exception {
		Map<String, Object> data = new HashMap<>();
		try {
			PorFileVO vo = new PorFileVO();
			vo.setUnqKey(fileId);
			PorFileVO fileInfo = porBoardFileService.selectBoardFile(vo);
			if (fileInfo != null) {
				porFileUtils.deleteEditorImage(fileInfo);
			}
			data.put("result", "success");
			return ResponseEntity.ok(DefaultApiResponse.success(data));
		} catch (Exception e) {
			return ResponseEntity.badRequest()
					.body(DefaultApiResponse.error(500, "파일 삭제에 실패했습니다.", e.getMessage()));
		}
	}

	@GetMapping(value = "/file.do")
	public void file(
			@ModelAttribute PorFileVO porFileVO
			, HttpServletRequest request
			, HttpServletResponse response) throws Exception {
		
		try {
			PorFileVO vo = porBoardFileService.selectBoardFile(porFileVO);
			if (vo == null) {
				throw new FileNotFoundException("파일 정보를 찾을 수 없습니다.");
			}

			String fileStreCours = EgovWebUtil.filePathBlackList(vo.getAtchFilePathNm());
			String streFileNm = EgovWebUtil.filePathBlackList(vo.getSrvrAtchFileNm());

			File uFile = new File(fileStreCours, streFileNm);
			long fSize = uFile.length();

			if (fSize > 0) {
				String mimetype = "application/x-stuff";

				response.setContentType(mimetype);
				setDisposition(vo.getOrgnlAtchFileNm(), request, response);

				try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(uFile));
					 BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream())) {
					FileCopyUtils.copy(in, out);
					out.flush();
				} catch (FileNotFoundException ex) {
				}
			} else {
				throw new FileNotFoundException("파일이 존재하지 않거나 크기가 0입니다.");
			}

		} catch (IllegalArgumentException e) {
			// Base64 디코딩 실패 등
			throw new FileNotFoundException("잘못된 atchFileId 인코딩입니다.");
		} catch (Exception e) {
			throw e;
		}
	}

	/**
	 * pstCn: Base64 디코딩 후 OWASP HTML Sanitizer로 정화 (MRB 방식, ModSecurity XSS 오탐 회피)
	 */
	private void decodeAndSanitizePstCn(PorBoardVO porBoardVO) {
		String pstCn = porBoardVO.getPstCn();
		if (pstCn == null || pstCn.isEmpty()) {
			return;
		}
		try {
			String decoded = new String(Base64.getDecoder().decode(pstCn.trim()), StandardCharsets.UTF_8);
			porBoardVO.setPstCn(EditorHtmlSanitizerUtil.sanitize(decoded));
		} catch (IllegalArgumentException e) {
			// Base64가 아니면(raw HTML 등) 그대로 정화만
			porBoardVO.setPstCn(EditorHtmlSanitizerUtil.sanitize(pstCn));
		}
	}
	
	/**
	 * Disposition 지정하기.
	 *
	 * @param filename
	 * @param request
	 * @param response
	 * @throws Exception
	 */
	private void setDisposition(String filename, HttpServletRequest request, HttpServletResponse response)
		throws Exception {
		String browser = getBrowser(request);

		String dispositionPrefix = "attachment; filename=";
		String encodedFilename = null;

		if (browser.equals("MSIE")) {
			encodedFilename = URLEncoder.encode(filename, "UTF-8").replaceAll("\\+", "%20");
		} else if (browser.equals("Trident")) { // IE11 문자열 깨짐 방지
			encodedFilename = URLEncoder.encode(filename, "UTF-8").replaceAll("\\+", "%20");
		} else if (browser.equals("Firefox")) {
			encodedFilename = "\"" + new String(filename.getBytes("UTF-8"), "8859_1") + "\"";
		} else if (browser.equals("Opera")) {
			encodedFilename = "\"" + new String(filename.getBytes("UTF-8"), "8859_1") + "\"";
		} else if (browser.equals("Chrome")) {
			StringBuffer sb = new StringBuffer();
			for (int i = 0; i < filename.length(); i++) {
				char c = filename.charAt(i);
				if (c > '~') {
					sb.append(URLEncoder.encode("" + c, "UTF-8"));
				} else {
					sb.append(c);
				}
			}
			encodedFilename = sb.toString();
		} else {
			//throw new RuntimeException("Not supported browser");
			throw new IOException("Not supported browser");
		}

		response.setHeader("Content-Disposition", dispositionPrefix + encodedFilename);

		if ("Opera".equals(browser)) {
			response.setContentType("application/octet-stream;charset=UTF-8");
		}
	}
	
	private String getBrowser(HttpServletRequest request) {
		String header = request.getHeader("User-Agent");
		if (header.indexOf("MSIE") > -1) {
			return "MSIE";
		} else if (header.indexOf("Trident") > -1) { // IE11 문자열 깨짐 방지
			return "Trident";
		} else if (header.indexOf("Chrome") > -1) {
			return "Chrome";
		} else if (header.indexOf("Opera") > -1) {
			return "Opera";
		}
		return "Firefox";
	}
}