package egovframework.com.its.web.server.service;

import egovframework.com.its.web.server.mapper.CommonMapper;
import egovframework.com.its.web.server.mapper.TrafficStatsMapper;
import egovframework.com.its.web.server.vo.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.function.Supplier;

@RequiredArgsConstructor
@Service
@Slf4j
public class TrafficStatsService {

    private final TrafficStatsMapper trafficStatsMapper;
    private final CommonMapper commonMapper;
    @Value("${statistics.max-records}")
    String statLimit;

    public Map<String, Object> getAtrdStat(String[] atrdIds, String from, String to, String type) throws IOException {
        if (type.isEmpty() || atrdIds.length == 0 || from.isEmpty() || to.isEmpty()) {
            throw new NullPointerException("파라미터 정보를 확인 해주세요.");
        }
        Map<String, Object> resultMap = new HashMap<>();
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("atrdIds", atrdIds);
        paramMap.put("from", from);
        paramMap.put("to", to);
        paramMap.put("type", type);

        if (!type.equals("sYear")) {
            LocalDateTime startDateTime = getDateValue(from);
            LocalDateTime endDateTime = getDateValue(to);
            LocalDateTime limitDate = getDateValue(from);
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            int limit = Integer.parseInt(this.statLimit);
            int total = this.trafficStatsMapper.getRoadCount(atrdIds);
            int dataCount = limit / total;
            long between = 0;
            int addTimes =  (int)Math.floor(dataCount);

            switch (type) {
                case "sTime":
                    between = ChronoUnit.HOURS.between(startDateTime, endDateTime);
                    limitDate = limitDate.plusHours(addTimes);
                    break;
                case "sDay":
                    between = ChronoUnit.DAYS.between(startDateTime, endDateTime);
                    limitDate = limitDate.plusDays(addTimes);
                    break;
                case "sMonth":
                    between = ChronoUnit.MONTHS.between(startDateTime, endDateTime);
                    limitDate = limitDate.plusMonths(addTimes);
                    break;
            }

            if (dataCount < between) {
                String limitDateStr = limitDate.format(formatter);
                log.error("조회 하신 기간이 데이터 조회 범위를 초과하였습니다. \n" + limitDateStr + " 이내로 조회해주세요.");
                throw new IllegalArgumentException("조회 하신 기간이 데이터 조회 범위를 초과하였습니다. "+
                        "\n조회 기간 : " + startDateTime.format(formatter) + " ~ " + endDateTime.format(formatter) +
                        "\n가능 데이터 조회 범위 : " + startDateTime.format(formatter) + " ~ " +  limitDateStr);
            }
        }

        List<AtrdStatVO> list = this.trafficStatsMapper.getAtrdStat(paramMap);
        String base64 = getAtrdExcel(list, type);
        resultMap.put("list", list);
        resultMap.put("excel", base64);
        return resultMap;
    }

    public List<AtrdVO> getAtrdNm() {
        return this.commonMapper.getAtrdNm();
    }

//    public List<ScIxrTrafficVO> getIxrTrafficStat(String[] ixrIds, String from, String to, String type) throws IOException {
    public Map<String, Object> getIxrTrafficStat(String[] ixrIds, String from, String to, String type) throws IOException {
//        long start = System.currentTimeMillis();
        if (from == null || to == null || ixrIds.length == 0 || type == null) {
            throw new NullPointerException("파라미터 정보를 확인 해주세요");
        }
        int total = this.trafficStatsMapper.getCameraCount(ixrIds);
        if (total == 0) {
            throw new NullPointerException("설치된 카메라 정보를 조회 할 수 없습니다.");
        }

        int limit = Integer.parseInt(statLimit);

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        long between = 0;
        int hourPerCount = (type.equals("15M")) ? 4 : 1;
        LocalDateTime startDateTime = getDateValue(from);
        LocalDateTime endDateTime = getDateValue(to);
        LocalDateTime limitDate = getDateValue(from);
        int dataCount = limit / (total * hourPerCount);
        int addTimes =  (int)Math.floor(dataCount);
        if (type.equals("15M") || type.equals("HH")) {
            between = ChronoUnit.HOURS.between(startDateTime, endDateTime);
            limitDate = limitDate.plusHours(addTimes);
        }
        else if (type.equals("DD")) {
            between = ChronoUnit.DAYS.between(startDateTime, endDateTime);
            limitDate = limitDate.plusDays(addTimes);
        }
        else if (type.equals("MN")) {
            between = ChronoUnit.MONTHS.between(startDateTime, endDateTime);
            limitDate = limitDate.plusMonths(addTimes);
        }

        if (dataCount < between) {
            String limitDateStr = limitDate.format(formatter);
            log.error("조회하신 기간이 데이터 조회 범위를 초과하였습니다. \n" + limitDateStr + " 이내로 조회해주세요.");
            throw new IllegalArgumentException("조회하신 기간이 데이터 조회 범위를 초과하였습니다."+
                    "\n조회 기간 : " + startDateTime.format(formatter) + " ~ " + endDateTime.format(formatter) +
                    "\n가능 데이터 조회 범위 : " + startDateTime.format(formatter) + " ~ " +  limitDateStr);
        }

        Map<String, Object> paramMap = new HashMap<>();
        List<ScIxrTrafficVO> result = null;
        paramMap.put("ixrIds", ixrIds);
        paramMap.put("from", from);
        paramMap.put("to", to);
        Map<String, Supplier<List<ScIxrTrafficVO>>> methodMap = new HashMap<>();
        methodMap.put("15M", () -> this.trafficStatsMapper.findIxr15M(paramMap));
        methodMap.put("HH", () -> this.trafficStatsMapper.findIxrHH(paramMap));
        methodMap.put("DD", () -> this.trafficStatsMapper.findIxrDD(paramMap));
        methodMap.put("MN", () -> this.trafficStatsMapper.findIxrMN(paramMap));

        result = methodMap.get(type).get();
        Map<String, Object> resultMap = new HashMap<>();
        String base64 = downExcel(result, type);
        resultMap.put("list", result);
        resultMap.put("excel", base64);
//        log.info("소요시간 {}ms", System.currentTimeMillis() - start);
        return resultMap;
    }

    public LocalDateTime getDateValue(String word) {
        int year       = Integer.parseInt(word.substring(0,4));
        int month      = Integer.parseInt(word.substring(4,6));
        int day        = Integer.parseInt(word.substring(6,8));
        int hour       = Integer.parseInt(word.substring(8,10));
        int minute     = Integer.parseInt(word.substring(10,12));
        int seconds    = Integer.parseInt(word.substring(12,14));
        return LocalDateTime.of(year, month, day, hour, minute, seconds);
    }

    public String downExcel(List<ScIxrTrafficVO> list, String type) throws IOException{
        if (list != null && list.size() > 0) {
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                Workbook workbook = new SXSSFWorkbook(2000);
                Sheet sheet = workbook.createSheet("교차로 교통량 통계"); // 시트명 지정
                Row headerRow = sheet.createRow(0);
                String[] columns = { "ID", "교차로명", "카메라방향", "방향", "통계시각", "좌회전(소형)", "좌회전(중형)", "좌회전(대형)", "직진(소형)",
                        "직진(중형)", "직진(대형)", "우회전(소형)", "우회전(중형)", "우회전(대형)", "유턴(소형)", "유턴(중형)", "유턴(대형)", "합계"};
                for (int i = 0; i < columns.length; i++) {
                    headerRow.createCell(i).setCellValue(columns[i]);
                }
                int rowNum = 1;
                DateTimeFormatter inputFormatter  = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
                String pattern = "yyyy-MM-dd HH:mm:ss";
                if (type.equals("MN")) {
                    pattern = "yyyy년 MM월";
                }
                else if (type.equals("DD")) {
                    pattern = "yyyy/MM/dd";
                }

                DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern(pattern);

                for (ScIxrTrafficVO vo : list) {
                    Row row = sheet.createRow(rowNum);
                    String clctDt = vo.getClctDt();
                    LocalDateTime dateTime = LocalDateTime.parse(clctDt, inputFormatter);
                    clctDt = dateTime.format(outputFormatter);
                    row.createCell(0).setCellValue(vo.getIxrId());
                    row.createCell(1).setCellValue(vo.getIxrNm());
                    row.createCell(2).setCellValue(vo.getCmraNm());
                    row.createCell(3).setCellValue(vo.getDrctDvsnCdR());
                    row.createCell(4).setCellValue(clctDt);
                    row.createCell(5).setCellValue(vo.getSmalLeftTfvl());
                    row.createCell(6).setCellValue(vo.getMddlLeftTfvl());
                    row.createCell(7).setCellValue(vo.getLrgLeftTfvl());
                    row.createCell(8).setCellValue(vo.getSmalGoTfvl());
                    row.createCell(9).setCellValue(vo.getMddlGoTfvl());
                    row.createCell(10).setCellValue(vo.getLrgGoTfvl());
                    row.createCell(11).setCellValue(vo.getSmalRghtTfvl());
                    row.createCell(12).setCellValue(vo.getMddlRghtTfvl());
                    row.createCell(13).setCellValue(vo.getLrgRghtTfvl());
                    row.createCell(14).setCellValue(vo.getSmalUturnTfvl());
                    row.createCell(15).setCellValue(vo.getMddlUturnTfvl());
                    row.createCell(16).setCellValue(vo.getLrgUturnTfvl());
                    row.createCell(17).setCellValue(vo.getTotTfvl());
                    if (rowNum % 2000 == 0) {  // 500개 행마다 flush
                        ((SXSSFSheet) sheet).flushRows(2000);
                    }
                    rowNum++;
                }

                workbook.write(outputStream);
                workbook.close();
                // Base64 인코딩
                String base64Encoded = Base64.getEncoder().encodeToString(outputStream.toByteArray());
                outputStream.close();
                return base64Encoded;
        }
        return null;
    }

    public String getAtrdExcel(List<AtrdStatVO> list, String type) throws IOException{
        if (list != null && list.size() > 0) {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            Workbook workbook = new SXSSFWorkbook(2000);
            Sheet sheet = workbook.createSheet("소통 통계"); // 시트명 지정
            Row headerRow = sheet.createRow(0);
            String[] columns = { "도로명", "구간", "통계시각", "속도(km/h)", "통행시간(sec)"};
            for (int i = 0; i < columns.length; i++) {
                headerRow.createCell(i).setCellValue(columns[i]);
            }
            int rowNum = 1;

            DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
            String pattern = "yyyy-MM-dd HH:mm:ss";
            if (type.equals("sYear")) {
                pattern = "yyyy년";
            }
            else if (type.equals("sMonth")) {
                pattern = "yyyy년 MM월";
            }
            else if (type.equals("sDay")) {
                pattern = "yyyy/MM/dd";
            }

            DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern(pattern);
            for (AtrdStatVO vo : list) {
                Row row = sheet.createRow(rowNum);
                String statDt = vo.getSTAT_DT();

                LocalDateTime dateTime = LocalDateTime.parse(statDt, inputFormatter);
                statDt = dateTime.format(outputFormatter);
                row.createCell(0).setCellValue(vo.getROAD_NM());
                row.createCell(1).setCellValue(vo.getSTRT_NM() + " - " + vo.getEND_NM());
                row.createCell(2).setCellValue(statDt);
                row.createCell(3).setCellValue(vo.getSPED());
                row.createCell(4).setCellValue(vo.getTRVL_HH());
                if (rowNum % 2000 == 0) {  // 500개 행마다 flush
                    ((SXSSFSheet) sheet).flushRows(2000);
                }
                rowNum++;
            }
            workbook.write(outputStream);
            workbook.close();
            // Base64 인코딩
            String base64Encoded = Base64.getEncoder().encodeToString(outputStream.toByteArray());
            outputStream.close();
            return base64Encoded;
        }
        return null;
    }

    public List<ScIxrVO> getIxrList() {
        return this.trafficStatsMapper.getIxrList();
    }

    public void setDateParam(Model model) {
        Calendar cal = Calendar.getInstance();
        int thisYear = cal.get(Calendar.YEAR);
        int thisMonth = cal.get(Calendar.MONTH) + 1;
        int thisDate = cal.get(Calendar.DATE);
        int thisHour = cal.get(Calendar.HOUR_OF_DAY);
        int lastDate = 31;

        model.addAttribute("thisYear", thisYear);
        model.addAttribute("thisMonth", thisMonth);
        model.addAttribute("thisDate", thisDate);
        model.addAttribute("thisHour", thisHour);
        model.addAttribute("lastDate", lastDate);
    }
}
