package go.kr.project.carInspectionPenalty.registration.controller;

import egovframework.constant.MessageConstants;
import egovframework.constant.TilesConstants;
import egovframework.util.ApiResponseUtil;
import egovframework.util.SessionUtil;
import egovframework.util.excel.ExcelSheetData;
import egovframework.util.excel.SxssfExcelFile;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtExcelVO;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtModifiedDataVO;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
import go.kr.project.carInspectionPenalty.registration.service.CarFfnlgTrgtService;
import go.kr.project.common.model.CmmnCodeSearchVO;
import go.kr.project.common.service.CommonCodeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 자동차 지연 과태료 대상 등록 Controller
 * 지연 과태료 대상 목록 조회, TXT 파일 업로드 기능 제공
 */
@Controller
@RequestMapping("/carInspectionPenalty/registration")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "자동차 지연 과태료 대상 등록", description = "자동차 지연 과태료 대상 등록 및 목록 조회 API")
public class CarFfnlgTrgtController {

    private final CarFfnlgTrgtService service;
    private final CommonCodeService commonCodeService;
    
    /**
     * 지연 과태료 대상 목록 화면
     * @param model 모델
     * @return 목록 화면 경로
     */
    @GetMapping("/list.do")
    @Operation(summary = "지연 과태료 대상 목록 화면", description = "지연 과태료 대상 목록 조회 화면을 제공합니다.")
    public String list(Model model) {
        log.debug("지연 과태료 대상 목록 화면 요청");
        
        // 업무 처리 상태 코드 조회 (공통코드)
        CmmnCodeSearchVO taskPrcsSttsCdSearchVO = CmmnCodeSearchVO.builder()
                .searchCdGroupId("TASK_PRCS_STTS_CD")
                .searchUseYn("Y")
                .sortColumn("SORT_ORDR")
                .sortAscending(true)
                .build();
        model.addAttribute("taskPrcsSttsCdList", commonCodeService.selectCodeDetailList(taskPrcsSttsCdSearchVO));

        // 지연 과태료 대상 구분 코드 조회 (공통코드)
        CmmnCodeSearchVO ffnlgTrgtSeCdSearchVO = CmmnCodeSearchVO.builder()
                .searchCdGroupId("FFNLG_TRGT_SE_CD")
                .searchUseYn("Y")
                .sortColumn("SORT_ORDR")
                .sortAscending(true)
                .build();
        model.addAttribute("ffnlgTrgtSeCdList", commonCodeService.selectCodeDetailList(ffnlgTrgtSeCdSearchVO));
        
        return "carInspectionPenalty/registration/list" + TilesConstants.BASE;
    }
    
    /**
     * 지연 과태료 대상 목록 조회 AJAX
     * @param paramVO 검색 조건
     * @return 목록 데이터
     */
    @PostMapping("/list.ajax")
    @Operation(summary = "지연 과태료 대상 목록 조회", description = "지연 과태료 대상 목록을 조회하고 JSON 형식으로 반환합니다.")
    public ResponseEntity<?> listAjax(@ModelAttribute CarFfnlgTrgtVO paramVO) {
        log.debug("지연 과태료 대상 목록 조회 AJAX - 검색조건: {}", paramVO);
        
        // 1. 총 개수 조회
        int totalCount = service.selectListTotalCount(paramVO);
        
        // 2. totalCount 설정
        paramVO.setTotalCount(totalCount);
        
        // 목록 조회
        List<CarFfnlgTrgtVO> list = service.selectList(paramVO);
        
        return ApiResponseUtil.successWithGrid(list, paramVO);
    }

    /**
     * 지연 과태료 대상 목록 다운로드 (EUC-KR 텍스트)
     * 샘플 파일과 동일한 고정폭 포맷으로 생성하여 다운로드 제공합니다.
     * - 인코딩: EUC-KR (한글 2바이트)
     * - 헤더/구분선/컬럼 구성은 docs/샘플용-EUC-KR.txt와 동일
     */
    @GetMapping("/download.do")
    @Operation(summary = "지연 과태료 대상 목록 다운로드", description = "EUC-KR 인코딩의 고정폭 텍스트로 목록을 샘플과 동일한 포맷으로 다운로드합니다.")
    public void download(
            @ModelAttribute CarFfnlgTrgtVO paramVO,
            HttpServletResponse response
    ) {
        try {

            // 페이징 없이 전체 조회를 위해 페이징 비활성화
            paramVO.setPagingYn("N");

            // 서비스에서 EUC-KR 텍스트 콘텐츠 생성
            byte[] fileBytes = service.generateEucKrDownloadBytes(paramVO);

            // EUC-KR 바이트를 UTF-8 바이트로 변환 (다운로드 시에만)
            String content = new String(fileBytes, "EUC-KR");
            // TODO : ECU-KR -> UTF-8 변환
            byte[] utfFileBytes = content.getBytes(StandardCharsets.UTF_8);

            String fileName = URLEncoder.encode("유효기간경과_과태료부과대상_리스트.prn", "UTF-8");

            // 응답 헤더 설정 (텍스트 파일, UTF-8 인코딩)
            response.setContentType("text/plain; charset=UTF-8");
            response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
            response.setContentLength(utfFileBytes.length);

            // UTF-8 바이트 스트림으로 전송
            response.getOutputStream().write(utfFileBytes);
            response.getOutputStream().flush();
        } catch (Exception e) {
            log.error("목록 다운로드 중 오류", e);
            try {
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                response.getWriter().write("다운로드 처리 중 오류가 발생했습니다: " + e.getMessage());
            } catch (Exception ignored) {

            }
        }
    }
    
    /**
     * 파일 업로드 팝업 화면
     * @return 팝업 화면
     */
    @GetMapping("/uploadPopup.do")
    @Operation(summary = "파일 업로드 팝업", description = "TXT, PRN 파일 업로드 팝업 화면을 제공합니다.")
    public ModelAndView uploadPopup() {
        log.debug("파일 업로드 팝업 화면 요청");
        
        ModelAndView mav = new ModelAndView("carInspectionPenalty/registration/uploadPopup" + TilesConstants.POPUP);
        
        return mav;
    }
    
    /**
     * TXT 파일 업로드 및 처리
     * 
     * 주의: 파일 단위로 업로드하므로 한 건이라도 실패하면 전체 롤백 처리됨
     * 
     * @param file 업로드된 TXT 파일
     * @return 처리 결과
     */
    @PostMapping("/upload.ajax")
    @Operation(summary = "TXT, PRN 파일 업로드", description = "TXT, PRN 파일을 업로드하고 파싱하여 DB에 저장합니다. 한 건이라도 실패 시 전체 롤백됩니다.")
    public ResponseEntity<?> upload(
            @Parameter(description = "TXT, PRN 파일") @RequestParam("file") MultipartFile file) {
        
        log.info("TXT, PRN 파일 업로드 요청 - 파일명: {}", file != null ? file.getOriginalFilename() : "null");
        
        try {
            // 세션에서 사용자 ID 가져오기
            String rgtr = SessionUtil.getUserId();
            if (rgtr == null || rgtr.isEmpty()) {
                return ApiResponseUtil.error("로그인 정보가 없습니다.");
            }

            // TODO : UTF-8 파일을 EUC-KR로 변환 (시스템은 EUC-KR 기준으로 처리)
            MultipartFile convertedFile = convertUtf8ToEucKr(file);

            // 파일 업로드 및 파싱 (한 건이라도 실패 시 전체 롤백)
            Map<String, Object> result = service.uploadAndParseTxtFile(convertedFile, rgtr);
            
            boolean success = (boolean) result.get("success");
            int successCount = (int) result.get("successCount");
            @SuppressWarnings("unchecked")
            List<String> errorMessages = (List<String>) result.get("errorMessages");
            
            if (success) {
                // 모든 데이터가 성공적으로 저장됨
                String message = String.format("파일 업로드가 완료되었습니다.\n\n성공: %d건", successCount);
                return ApiResponseUtil.success(result, message);
            } else {
                // 파일 검증 오류 (트랜잭션 시작 전 오류)
                StringBuilder message = new StringBuilder();
                if (!errorMessages.isEmpty()) {
                    for (String errorMsg : errorMessages) {
                        message.append(errorMsg).append("\n");
                    }
                } else {
                    message.append("파일 업로드 중 오류가 발생했습니다.");
                }
                return ApiResponseUtil.error(message.toString());
            }
            
        } catch (RuntimeException e) {
            // 데이터 처리 중 오류 발생 - 전체 롤백됨
            log.error("TXT, PRN 파일 업로드 중 오류 발생 - 전체 롤백", e);
            return ApiResponseUtil.error(e.getMessage());
        } catch (Exception e) {
            // 예상치 못한 오류
            log.error("TXT, PRN 파일 업로드 중 예상치 못한 오류 발생", e);
            return ApiResponseUtil.error("파일 업로드 중 오류가 발생했습니다: " + e.getMessage());
        }
    }
    
    /**
     * 지연 과태료 대상 상세 조회
     * @param carFfnlgTrgtId 지연 과태료 대상 ID
     * @return 상세 정보
     */
    @GetMapping("/selectOne.ajax")
    @Operation(summary = "지연 과태료 대상 상세 조회", description = "지연 과태료 대상 상세 정보를 조회합니다.")
    public ResponseEntity<?> selectOne(
            @Parameter(description = "지연 과태료 대상 ID") @RequestParam String carFfnlgTrgtId) {
        
        log.debug("지연 과태료 대상 상세 조회 - ID: {}", carFfnlgTrgtId);
        
        try {
            CarFfnlgTrgtVO vo = new CarFfnlgTrgtVO();
            vo.setCarFfnlgTrgtId(carFfnlgTrgtId);
            
            CarFfnlgTrgtVO result = service.selectOne(vo);
            
            if (result != null) {
                return ApiResponseUtil.success(result, "조회 성공");
            } else {
                return ApiResponseUtil.error("조회된 데이터가 없습니다.");
            }
            
        } catch (Exception e) {
            log.error("지연 과태료 대상 상세 조회 중 오류 발생", e);
            return ApiResponseUtil.error("조회 중 오류가 발생했습니다: " + e.getMessage());
        }
    }
    
    /**
     * 지연 과태료 대상 삭제 (논리삭제)
     * @param carFfnlgTrgtId 지연 과태료 대상 ID
     * @return 삭제 결과
     */
    @PostMapping("/delete.ajax")
    @Operation(summary = "지연 과태료 대상 삭제", description = "지연 과태료 대상을 삭제(논리삭제)합니다.")
    public ResponseEntity<?> delete(
            @Parameter(description = "지연 과태료 대상 ID") @RequestParam String carFfnlgTrgtId) {

        log.info("지연 과태료 대상 삭제 요청 - ID: {}", carFfnlgTrgtId);

        try {
            String dltr = SessionUtil.getUserId();
            if (dltr == null || dltr.isEmpty()) {
                return ApiResponseUtil.error("로그인 정보가 없습니다.");
            }

            CarFfnlgTrgtVO vo = new CarFfnlgTrgtVO();
            vo.setCarFfnlgTrgtId(carFfnlgTrgtId);
            vo.setDltr(dltr);

            int result = service.delete(vo);

            if (result > 0) {
                return ApiResponseUtil.success(MessageConstants.Common.DELETE_SUCCESS);
            } else {
                return ApiResponseUtil.error(MessageConstants.Common.DELETE_ERROR);
            }

        } catch (Exception e) {
            log.error("지연 과태료 대상 삭제 중 오류 발생", e);
            return ApiResponseUtil.error("삭제 중 오류가 발생했습니다: " + e.getMessage());
        }
    }

    /**
     * 선택된 목록에 대해 API 호출 및 기본정보/등록원부 비교
     * @param targetList 선택된 지연 과태료 대상 목록
     * @return 비교 결과
     */
    @PostMapping("/compareWithApi.ajax")
    @ResponseBody
    @Operation(summary = "API 호출 및 데이터 비교", description = "선택된 목록에 대해 차량 API를 호출하고 기본정보 및 등록원부와 비교합니다.")
    public ResponseEntity<?> compareWithApi(@RequestBody List<Map<String, String>> targetList) {
        log.info("API 호출 및 비교 요청 - 선택된 데이터 건수: {}", targetList != null ? targetList.size() : 0);

        try {
            Map<String, Object> resultData = service.compareWithApi(targetList);

            int successCount = (int) resultData.get("successCount");
            int failCount = (int) resultData.get("failCount");
            String message = String.format("API 호출 및 비교 완료\n성공: %d건, 실패: %d건", successCount, failCount);

            return ApiResponseUtil.success(resultData, message);

        } catch (IllegalArgumentException e) {
            log.error("파라미터 검증 오류", e);
            return ApiResponseUtil.error(e.getMessage());
        } catch (Exception e) {
            log.error("API 호출 및 비교 중 오류 발생", e);
            return ApiResponseUtil.error("처리 중 오류가 발생했습니다: " + e.getMessage());
        }
    }

    /**
     * 검색조건 전체 목록에 대해 API 호출 및 기본정보/등록원부 비교 (페이징 없이)
     * @param searchParams 검색 조건 파라미터
     * @return 비교 결과
     */
    @PostMapping("/compareWithApiAll.ajax")
    @ResponseBody
    @Operation(summary = "검색조건 전체 API 호출 및 데이터 비교", description = "검색조건에 해당하는 전체 목록에 대해 차량 API를 호출하고 비교합니다.")
    public ResponseEntity<?> compareWithApiAll(@RequestBody CarFfnlgTrgtVO searchParams) {
        log.info("전체 API 호출 및 비교 요청 - 검색 조건: {}", searchParams);

        try {
            // 페이징 비활성화
            searchParams.setPagingYn("N");

            // 전체 목록 조회
            List<CarFfnlgTrgtVO> allData = service.selectList(searchParams);

            // 목록을 Map 형태로 변환
            List<Map<String, String>> targetList = allData.stream()
                    .map(vo -> {
                        Map<String, String> map = new HashMap<>();
                        map.put("carFfnlgTrgtId", vo.getCarFfnlgTrgtId());
                        map.put("vhclno", vo.getVhclno());
                        map.put("inspYmd", vo.getInspYmd());
                        map.put("ownrNm", vo.getOwnrNm());
                        map.put("carNm", vo.getCarNm());
                        return map;
                    })
                    .collect(java.util.stream.Collectors.toList());

            // API 호출 및 비교
            Map<String, Object> resultData = service.compareWithApi(targetList);

            int successCount = (int) resultData.get("successCount");
            int failCount = (int) resultData.get("failCount");
            String message = String.format("전체 API 호출 및 비교 완료\n대상: %d건, 성공: %d건, 실패: %d건",
                    allData.size(), successCount, failCount);

            return ApiResponseUtil.success(resultData, message);

        } catch (Exception e) {
            log.error("전체 API 호출 및 비교 중 오류 발생", e);
            return ApiResponseUtil.error("처리 중 오류가 발생했습니다: " + e.getMessage());
        }
    }

    /**
     * 지연 과태료 대상 일괄 삭제
     * @param deleteIds 삭제할 지연 과태료 대상 ID 목록
     * @return 삭제 결과
     */
    @PostMapping("/deleteBatch.ajax")
    @ResponseBody
    @Operation(summary = "지연 과태료 대상 일괄 삭제", description = "선택된 지연 과태료 대상 목록을 일괄 삭제합니다.")
    public ResponseEntity<?> deleteBatch(@RequestBody List<String> deleteIds) {
        log.info("일괄 삭제 요청 - 선택된 데이터 건수: {}", deleteIds != null ? deleteIds.size() : 0);

        try {
            if (deleteIds == null || deleteIds.isEmpty()) {
                return ApiResponseUtil.error("삭제할 데이터가 없습니다.");
            }

            int successCount = 0;
            int failCount = 0;

            for (String id : deleteIds) {
                try {
                    CarFfnlgTrgtVO vo = new CarFfnlgTrgtVO();
                    vo.setCarFfnlgTrgtId(id);
                    int result = service.delete(vo);

                    if (result > 0) {
                        successCount++;
                    } else {
                        failCount++;
                    }
                } catch (Exception e) {
                    log.error("삭제 실패 - ID: {}", id, e);
                    failCount++;
                }
            }

            String message = String.format("삭제 완료\n성공: %d건, 실패: %d건", successCount, failCount);

            Map<String, Object> resultData = new HashMap<>();
            resultData.put("successCount", successCount);
            resultData.put("failCount", failCount);

            return ApiResponseUtil.success(resultData, message);

        } catch (Exception e) {
            log.error("일괄 삭제 중 오류 발생", e);
            return ApiResponseUtil.error("삭제 중 오류가 발생했습니다: " + e.getMessage());
        }
    }

    /**
     * 지연 과태료 대상 정보를 일괄 저장하는 AJAX 메소드
     * 생성, 수정, 삭제된 데이터를 처리합니다.
     *
     * @param modifyData 생성/수정/삭제할 데이터를 담은 VO 객체
     * @return 저장 결과 메시지와 성공/실패 상태를 담은 ResponseEntity 객체
     */
    @PostMapping("/saveAll.ajax")
    @ResponseBody
    @Operation(summary = "지연 과태료 대상 정보 일괄 저장", description = "생성, 수정, 삭제된 지연 과태료 대상 데이터를 일괄 처리합니다.")
    public ResponseEntity<?> saveAllAjax(@RequestBody CarFfnlgTrgtModifiedDataVO modifyData) {
        log.info("지연 과태료 대상 일괄 저장 요청 - 수정: {}건, 생성: {}건, 삭제: {}건",
                modifyData.getUpdatedRows() != null ? modifyData.getUpdatedRows().size() : 0,
                modifyData.getCreatedRows() != null ? modifyData.getCreatedRows().size() : 0,
                modifyData.getDeletedRows() != null ? modifyData.getDeletedRows().size() : 0);

        try {
            int result = service.saveCarFfnlgTrgts(modifyData);
            if (result > 0) {
                return ApiResponseUtil.success("지연 과태료 대상 정보가 저장되었습니다.");
            } else {
                return ApiResponseUtil.error("저장할 데이터가 없습니다.");
            }
        } catch (Exception e) {
            log.error("지연 과태료 대상 일괄 저장 중 오류 발생", e);
            return ApiResponseUtil.error("저장 중 오류가 발생했습니다: " + e.getMessage());
        }
    }

    /**
     * UTF-8 인코딩된 MultipartFile을 EUC-KR 인코딩으로 변환
     *
     * @param utf8File UTF-8 인코딩된 원본 파일
     * @return EUC-KR 인코딩으로 변환된 파일
     * @throws IOException 파일 읽기/변환 중 오류 발생 시
     */
    private MultipartFile convertUtf8ToEucKr(MultipartFile utf8File) throws IOException {
        // UTF-8로 파일 내용 읽기
        String content = new String(utf8File.getBytes(), StandardCharsets.UTF_8);

        // EUC-KR 바이트로 변환
        byte[] eucKrBytes = content.getBytes("EUC-KR");
        String eucKrContent = new String(eucKrBytes, "EUC-KR");

        log.info("파일 인코딩 변환 - UTF-8({} bytes) → EUC-KR({} bytes)",
                utf8File.getSize(), eucKrBytes.length);

        // EUC-KR 바이트로 변환된 새로운 MultipartFile 반환
        return new EucKrMultipartFile(
                utf8File.getName(),
                utf8File.getOriginalFilename(),
                utf8File.getContentType(),
                eucKrBytes
        );
    }

    /**
     * EUC-KR 인코딩 변환을 위한 커스텀 MultipartFile 구현체
     */
    private static class EucKrMultipartFile implements MultipartFile {
        private final String name;
        private final String originalFilename;
        private final String contentType;
        private final byte[] bytes;

        public EucKrMultipartFile(String name, String originalFilename, String contentType, byte[] bytes) {
            this.name = name;
            this.originalFilename = originalFilename;
            this.contentType = contentType;
            this.bytes = bytes;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public String getOriginalFilename() {
            return originalFilename;
        }

        @Override
        public String getContentType() {
            return contentType;
        }

        @Override
        public boolean isEmpty() {
            return bytes == null || bytes.length == 0;
        }

        @Override
        public long getSize() {
            return bytes.length;
        }

        @Override
        public byte[] getBytes() {
            return bytes;
        }

        @Override
        public InputStream getInputStream() {
            return new ByteArrayInputStream(bytes);
        }

        @Override
        public void transferTo(java.io.File dest) throws IOException, IllegalStateException {
            try (java.io.FileOutputStream fos = new java.io.FileOutputStream(dest)) {
                fos.write(bytes);
            }
        }
    }

    /**
     * 지연 과태료 대상 목록 엑셀 다운로드
     *
     * @param paramVO 검색 조건을 담은 VO 객체
     * @param request HTTP 요청 객체
     * @param response HTTP 응답 객체
     */
    @PostMapping("/excel.do")
    @Operation(summary = "지연 과태료 대상 목록 엑셀 다운로드", description = "지연 과태료 대상 목록을 엑셀 파일로 다운로드합니다.")
    public void downloadExcel(
            @ModelAttribute CarFfnlgTrgtVO paramVO,
            HttpServletRequest request,
            HttpServletResponse response) {
        try {
            log.debug("지연 과태료 대상 목록 엑셀 다운로드 요청");

            // 페이징 처리 없이 전체 데이터 조회
            paramVO.setPagingYn("N");

            // 지연 과태료 대상 목록 조회
            List<CarFfnlgTrgtExcelVO> excelList = service.selectListForExcel(paramVO);

            // 엑셀 파일 생성 및 다운로드
            String filename = "과태료대상목록_" + java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx";
            new SxssfExcelFile(ExcelSheetData.of(excelList, CarFfnlgTrgtExcelVO.class, "지연 과태료 대상 목록 " + excelList.size() + "건"), request, response, filename);

            log.debug("지연 과태료 대상 목록 엑셀 다운로드 완료 - 파일명: {}, 건수: {}", filename, excelList.size());
        } catch (Exception e) {
            log.error("엑셀 다운로드 중 오류 발생", e);
        }
    }
}
