package research.loghunter.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import research.loghunter.entity.*;
import research.loghunter.repository.*;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class ScanService {

    private final ServerRepository serverRepository;
    private final ServerLogPathRepository logPathRepository;
    private final PatternRepository patternRepository;
    private final ScanHistoryRepository scanHistoryRepository;
    private final ErrorLogRepository errorLogRepository;
    private final ScannedFileRepository scannedFileRepository;
    private final SftpService sftpService;
    private final LogParserService logParserService;

    // 진행 상황 저장 (serverId -> ScanProgress)
    private final ConcurrentHashMap<Long, ScanProgress> progressMap = new ConcurrentHashMap<>();

    // 로그 시간 파싱용 패턴들
    private static final List<DateTimeFormatter> DATE_FORMATTERS = List.of(
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss,SSS"),  // 2026-01-06 09:57:01,114
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"),  // 2026-01-06 09:57:01.114
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),      // 2026-01-06 09:57:01
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"), // ISO format
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"),
            DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm:ss"),     // 06-Jan-2026 09:57:01
            DateTimeFormatter.ofPattern("MMM dd, yyyy HH:mm:ss"),    // Jan 06, 2026 09:57:01
            DateTimeFormatter.ofPattern("MMM dd HH:mm:ss")           // Jan 06 09:57:01 (년도 없음)
    );

    // 로그 라인에서 날짜 추출용 정규식
    private static final Pattern DATE_PATTERN = Pattern.compile(
            "(\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}[,.]?\\d{0,3})" +
            "|(\\d{2}-[A-Za-z]{3}-\\d{4} \\d{2}:\\d{2}:\\d{2})" +
            "|([A-Za-z]{3} \\d{2},? \\d{4} \\d{2}:\\d{2}:\\d{2})" +
            "|([A-Za-z]{3} \\d{2} \\d{2}:\\d{2}:\\d{2})"
    );

    /**
     * 단일 서버 스캔
     */
    @Transactional
    public ScanResult scanServer(Long serverId, Consumer<ScanProgress> progressCallback) {
        Server server = serverRepository.findById(serverId)
                .orElseThrow(() -> new RuntimeException("Server not found: " + serverId));

        return executeScan(server, progressCallback);
    }

    /**
     * 모든 활성 서버 스캔
     */
    @Transactional
    public List<ScanResult> scanAllServers(Consumer<ScanProgress> progressCallback) {
        List<Server> servers = serverRepository.findByActiveTrue();
        return servers.stream()
                .map(server -> executeScan(server, progressCallback))
                .toList();
    }

    /**
     * 스캔 실행
     */
    private ScanResult executeScan(Server server, Consumer<ScanProgress> progressCallback) {
        ScanProgress progress = new ScanProgress(server.getId(), server.getName());
        progressMap.put(server.getId(), progress);
        
        LocalDateTime scanStartTime = LocalDateTime.now();

        // 스캔 이력 생성
        ScanHistory history = ScanHistory.builder()
                .server(server)
                .startedAt(scanStartTime)
                .status("RUNNING")
                .build();
        history = scanHistoryRepository.save(history);

        try {
            // 1. 서버 시간 체크 (시차 확인)
            progress.setMessage("서버 시간 확인 중...");
            notifyProgress(progressCallback, progress);
            
            int timeOffsetMinutes = checkAndUpdateServerTimeOffset(server);
            log.info("Server {} time offset: {} minutes", server.getId(), timeOffsetMinutes);

            // 2. 활성 로그 경로 조회
            List<ServerLogPath> logPaths = logPathRepository.findByServerIdAndActiveTrue(server.getId());
            if (logPaths.isEmpty()) {
                throw new RuntimeException("등록된 로그 경로가 없습니다.");
            }

            // 3. 활성 패턴 조회
            List<research.loghunter.entity.Pattern> patterns = patternRepository.findByActiveTrue();
            if (patterns.isEmpty()) {
                throw new RuntimeException("등록된 패턴이 없습니다.");
            }

            progress.setTotalPaths(logPaths.size());
            notifyProgress(progressCallback, progress);

            AtomicInteger totalFilesScanned = new AtomicInteger(0);
            AtomicInteger totalFilesSkipped = new AtomicInteger(0);
            AtomicInteger totalErrorsFound = new AtomicInteger(0);

            for (ServerLogPath logPath : logPaths) {
                progress.setCurrentPath(logPath.getPath());
                progress.setMessage("파일 목록 조회 중...");
                notifyProgress(progressCallback, progress);

                try {
                    // 4. 해당 경로의 모든 파일 조회 (패턴 매칭)
                    List<SftpService.RemoteFile> allFiles = sftpService.listAllFiles(server, logPath);
                    
                    // 5. 스캔 대상 파일 필터링 (중복 제외)
                    List<SftpService.RemoteFile> filesToScan = new ArrayList<>();
                    for (SftpService.RemoteFile file : allFiles) {
                        if (shouldScanFile(server.getId(), logPath.getId(), file)) {
                            filesToScan.add(file);
                        } else {
                            totalFilesSkipped.incrementAndGet();
                        }
                    }

                    progress.setTotalFiles(progress.getTotalFiles() + filesToScan.size());
                    progress.setMessage(String.format("전체 %d개 중 %d개 스캔 대상 (기존 %d개 스킵)", 
                            allFiles.size(), filesToScan.size(), allFiles.size() - filesToScan.size()));
                    notifyProgress(progressCallback, progress);

                    for (SftpService.RemoteFile file : filesToScan) {
                        progress.setCurrentFile(file.name());
                        notifyProgress(progressCallback, progress);

                        try {
                            // 파일 내용 다운로드
                            String content = sftpService.downloadFileContent(server, file.path());

                            // 패턴 매칭
                            List<LogParserService.MatchResult> matches = 
                                    logParserService.parseAndMatch(content, patterns, file.path());

                            // 에러 저장
                            for (LogParserService.MatchResult match : matches) {
                                // 로그에서 시간 파싱 (시차 보정 적용)
                                LocalDateTime occurredAt = parseLogTime(
                                        match.context(), 
                                        file.modifiedAt(), 
                                        timeOffsetMinutes
                                );
                                
                                ErrorLog errorLog = ErrorLog.builder()
                                        .server(server)
                                        .pattern(match.pattern())
                                        .scanHistory(history)
                                        .filePath(match.filePath())
                                        .lineNumber(match.lineNumber())
                                        .summary(match.summary())
                                        .context(match.context())
                                        .severity(match.severity())
                                        .occurredAt(occurredAt)      // 로그 내 시간 (보정됨)
                                        .scannedAt(scanStartTime)    // 스캔 시작 시간
                                        .build();
                                errorLogRepository.save(errorLog);
                                totalErrorsFound.incrementAndGet();
                            }

                            // 스캔 완료된 파일 기록
                            saveScannedFile(server.getId(), logPath.getId(), file, matches.size());

                            totalFilesScanned.incrementAndGet();
                            progress.setScannedFiles(totalFilesScanned.get());
                            progress.setErrorsFound(totalErrorsFound.get());
                            notifyProgress(progressCallback, progress);

                        } catch (Exception e) {
                            log.warn("Failed to process file {}: {}", file.path(), e.getMessage());
                        }
                    }

                } catch (Exception e) {
                    log.warn("Failed to process log path {}: {}", logPath.getPath(), e.getMessage());
                }

                progress.setScannedPaths(progress.getScannedPaths() + 1);
                notifyProgress(progressCallback, progress);
            }

            // 스캔 완료
            history.setStatus("SUCCESS");
            history.setFinishedAt(LocalDateTime.now());
            history.setFilesScanned(totalFilesScanned.get());
            history.setErrorsFound(totalErrorsFound.get());
            scanHistoryRepository.save(history);

            // 서버 마지막 스캔 시간 업데이트
            server.setLastScanAt(LocalDateTime.now());
            if (totalErrorsFound.get() > 0) {
                server.setLastErrorAt(LocalDateTime.now());
            }
            serverRepository.save(server);

            progress.setStatus("SUCCESS");
            progress.setMessage(String.format("완료! (스캔: %d, 스킵: %d, 에러: %d)", 
                    totalFilesScanned.get(), totalFilesSkipped.get(), totalErrorsFound.get()));
            notifyProgress(progressCallback, progress);

            return new ScanResult(server.getId(), server.getName(), true, 
                    totalFilesScanned.get(), totalErrorsFound.get(), null);

        } catch (Exception e) {
            log.error("Scan failed for server {}: {}", server.getId(), e.getMessage());
            
            history.setStatus("FAILED");
            history.setFinishedAt(LocalDateTime.now());
            history.setErrorMessage(e.getMessage());
            scanHistoryRepository.save(history);

            progress.setStatus("FAILED");
            progress.setMessage("스캔 실패: " + e.getMessage());
            notifyProgress(progressCallback, progress);

            return new ScanResult(server.getId(), server.getName(), false, 0, 0, e.getMessage());
        } finally {
            progressMap.remove(server.getId());
        }
    }

    /**
     * 서버 시간 오프셋 확인 및 업데이트
     */
    private int checkAndUpdateServerTimeOffset(Server server) {
        try {
            SftpService.TimeCheckResult result = sftpService.checkServerTime(server);
            
            if (result.success()) {
                server.setTimeOffsetMinutes(result.offsetMinutes());
                server.setLastTimeSyncAt(LocalDateTime.now());
                serverRepository.save(server);
                
                log.info("Server {} time sync - Server: {}, Local: {}, Offset: {} min",
                        server.getName(), result.serverTime(), result.localTime(), result.offsetMinutes());
                
                return result.offsetMinutes();
            }
        } catch (Exception e) {
            log.warn("Failed to check server time for {}: {}", server.getId(), e.getMessage());
        }
        
        // 실패 시 기존 값 사용 (없으면 0)
        return server.getTimeOffsetMinutes() != null ? server.getTimeOffsetMinutes() : 0;
    }

    /**
     * 파일을 스캔해야 하는지 확인 (중복 체크)
     * - 파일경로 + 크기가 동일하면 스킵
     * - 파일경로는 같은데 크기가 다르면 재스캔
     */
    private boolean shouldScanFile(Long serverId, Long logPathId, SftpService.RemoteFile file) {
        Optional<ScannedFile> existing = scannedFileRepository.findByServerIdAndFilePathAndFileSize(
                serverId, file.path(), file.size());
        
        if (existing.isPresent()) {
            log.debug("Skipping already scanned file: {} (size: {})", file.path(), file.size());
            return false;
        }
        
        // 파일경로는 같은데 크기가 다른 경우 확인 (변경된 파일)
        List<ScannedFile> previousVersions = scannedFileRepository.findByServerIdAndFilePath(serverId, file.path());
        if (!previousVersions.isEmpty()) {
            log.info("File size changed, will rescan: {} (old: {}, new: {})", 
                    file.path(), previousVersions.get(0).getFileSize(), file.size());
        }
        
        return true;
    }

    /**
     * 스캔 완료된 파일 정보 저장
     */
    private void saveScannedFile(Long serverId, Long logPathId, SftpService.RemoteFile file, int errorCount) {
        ScannedFile scannedFile = new ScannedFile();
        scannedFile.setServerId(serverId);
        scannedFile.setLogPathId(logPathId);
        scannedFile.setFilePath(file.path());
        scannedFile.setFileName(file.name());
        scannedFile.setFileSize(file.size());
        scannedFile.setScannedAt(LocalDateTime.now());
        scannedFile.setErrorCount(errorCount);
        
        scannedFileRepository.save(scannedFile);
        log.debug("Saved scanned file record: {} (size: {}, errors: {})", file.path(), file.size(), errorCount);
    }

    /**
     * 로그 내용에서 시간 파싱 (시차 보정 적용)
     */
    private LocalDateTime parseLogTime(String logContent, LocalDateTime fallback, int offsetMinutes) {
        if (logContent == null || logContent.isEmpty()) {
            return applyOffset(fallback, offsetMinutes);
        }

        // 첫 줄에서 시간 추출 시도
        String[] lines = logContent.split("\n");
        for (String line : lines) {
            LocalDateTime parsed = extractDateTime(line);
            if (parsed != null) {
                return applyOffset(parsed, offsetMinutes);
            }
        }

        return applyOffset(fallback, offsetMinutes);
    }

    /**
     * 문자열에서 날짜/시간 추출
     */
    private LocalDateTime extractDateTime(String text) {
        Matcher matcher = DATE_PATTERN.matcher(text);
        if (!matcher.find()) {
            return null;
        }

        String dateStr = matcher.group();
        
        for (DateTimeFormatter formatter : DATE_FORMATTERS) {
            try {
                return LocalDateTime.parse(dateStr, formatter);
            } catch (DateTimeParseException e) {
                // 다음 포맷 시도
            }
        }

        return null;
    }

    /**
     * 시간에 오프셋 적용
     * offset > 0: 로컬이 서버보다 앞섬 (로그시간 + offset = 로컬시간)
     */
    private LocalDateTime applyOffset(LocalDateTime time, int offsetMinutes) {
        if (time == null || offsetMinutes == 0) {
            return time;
        }
        return time.plusMinutes(offsetMinutes);
    }

    /**
     * 현재 진행 상황 조회
     */
    public ScanProgress getProgress(Long serverId) {
        return progressMap.get(serverId);
    }

    /**
     * 스캔 이력 조회
     */
    public List<ScanHistory> getHistory(Long serverId) {
        return scanHistoryRepository.findByServerIdOrderByStartedAtDesc(serverId);
    }

    /**
     * 스캔된 파일 목록 조회 (서버별)
     */
    public List<ScannedFile> getScannedFiles(Long serverId) {
        return scannedFileRepository.findByServerId(serverId);
    }

    /**
     * 스캔된 파일 기록 초기화 (재스캔 허용)
     */
    @Transactional
    public void resetScannedFiles(Long serverId) {
        List<ScannedFile> files = scannedFileRepository.findByServerId(serverId);
        scannedFileRepository.deleteAll(files);
        log.info("Reset {} scanned file records for server {}", files.size(), serverId);
    }

    private void notifyProgress(Consumer<ScanProgress> callback, ScanProgress progress) {
        if (callback != null) {
            callback.accept(progress);
        }
    }

    // DTOs
    public static class ScanProgress {
        private Long serverId;
        private String serverName;
        private String status = "RUNNING";
        private String message;
        private String currentPath;
        private String currentFile;
        private int totalPaths;
        private int scannedPaths;
        private int totalFiles;
        private int scannedFiles;
        private int errorsFound;

        public ScanProgress(Long serverId, String serverName) {
            this.serverId = serverId;
            this.serverName = serverName;
        }

        // Getters and Setters
        public Long getServerId() { return serverId; }
        public String getServerName() { return serverName; }
        public String getStatus() { return status; }
        public void setStatus(String status) { this.status = status; }
        public String getMessage() { return message; }
        public void setMessage(String message) { this.message = message; }
        public String getCurrentPath() { return currentPath; }
        public void setCurrentPath(String currentPath) { this.currentPath = currentPath; }
        public String getCurrentFile() { return currentFile; }
        public void setCurrentFile(String currentFile) { this.currentFile = currentFile; }
        public int getTotalPaths() { return totalPaths; }
        public void setTotalPaths(int totalPaths) { this.totalPaths = totalPaths; }
        public int getScannedPaths() { return scannedPaths; }
        public void setScannedPaths(int scannedPaths) { this.scannedPaths = scannedPaths; }
        public int getTotalFiles() { return totalFiles; }
        public void setTotalFiles(int totalFiles) { this.totalFiles = totalFiles; }
        public int getScannedFiles() { return scannedFiles; }
        public void setScannedFiles(int scannedFiles) { this.scannedFiles = scannedFiles; }
        public int getErrorsFound() { return errorsFound; }
        public void setErrorsFound(int errorsFound) { this.errorsFound = errorsFound; }
    }

    public record ScanResult(
            Long serverId,
            String serverName,
            boolean success,
            int filesScanned,
            int errorsFound,
            String error
    ) {}

    // === 분석 결과 초기화 ===

    /**
     * 서버별 분석 결과 초기화
     */
    @Transactional
    public ResetResult resetScanData(Long serverId) {
        long deletedErrors = errorLogRepository.countByServerId(serverId);
        errorLogRepository.deleteByServerId(serverId);
        
        List<ScannedFile> scannedFiles = scannedFileRepository.findByServerId(serverId);
        int deletedFiles = scannedFiles.size();
        scannedFileRepository.deleteAll(scannedFiles);
        
        List<ScanHistory> histories = scanHistoryRepository.findByServerIdOrderByStartedAtDesc(serverId);
        int deletedHistories = histories.size();
        scanHistoryRepository.deleteAll(histories);
        
        log.info("Reset scan data for server {}: {} errors, {} files, {} histories", 
                serverId, deletedErrors, deletedFiles, deletedHistories);
        
        return new ResetResult((int) deletedErrors, deletedFiles, deletedHistories);
    }

    /**
     * 전체 분석 결과 초기화
     */
    @Transactional
    public ResetResult resetAllScanData() {
        long deletedErrors = errorLogRepository.count();
        errorLogRepository.deleteAll();
        
        long deletedFiles = scannedFileRepository.count();
        scannedFileRepository.deleteAll();
        
        long deletedHistories = scanHistoryRepository.count();
        scanHistoryRepository.deleteAll();
        
        log.info("Reset all scan data: {} errors, {} files, {} histories", 
                deletedErrors, deletedFiles, deletedHistories);
        
        return new ResetResult((int) deletedErrors, (int) deletedFiles, (int) deletedHistories);
    }

    // === 에러 통계 ===

    /**
     * 파일별 에러 통계
     */
    public List<FileErrorStats> getErrorStatsByFile(Long serverId) {
        List<Object[]> results = errorLogRepository.getErrorStatsByFile(serverId);
        return results.stream()
                .map(row -> new FileErrorStats(
                        (String) row[0],  // filePath
                        (String) row[1],  // serverName
                        ((Number) row[2]).intValue(),  // totalCount
                        ((Number) row[3]).intValue(),  // criticalCount
                        ((Number) row[4]).intValue(),  // errorCount
                        ((Number) row[5]).intValue(),  // warnCount
                        (LocalDateTime) row[6]         // lastOccurredAt
                ))
                .toList();
    }

    /**
     * 서버별 에러 통계
     */
    public List<ServerErrorStats> getErrorStatsByServer() {
        List<Object[]> results = errorLogRepository.getErrorStatsByServer();
        return results.stream()
                .map(row -> new ServerErrorStats(
                        ((Number) row[0]).longValue(), // serverId
                        (String) row[1],               // serverName
                        ((Number) row[2]).intValue(),  // totalCount
                        ((Number) row[3]).intValue(),  // criticalCount
                        ((Number) row[4]).intValue(),  // errorCount
                        ((Number) row[5]).intValue(),  // warnCount
                        (LocalDateTime) row[6]         // lastOccurredAt
                ))
                .toList();
    }

    /**
     * 패턴별 에러 통계
     */
    public List<PatternErrorStats> getErrorStatsByPattern(Long serverId) {
        List<Object[]> results = errorLogRepository.getErrorStatsByPattern(serverId);
        return results.stream()
                .map(row -> new PatternErrorStats(
                        ((Number) row[0]).longValue(), // patternId
                        (String) row[1],               // patternName
                        (String) row[2],               // severity
                        ((Number) row[3]).intValue(),  // count
                        (LocalDateTime) row[4]         // lastOccurredAt
                ))
                .toList();
    }

    /**
     * 대시보드용: 서버별 최근 N일 일별 통계
     */
    public List<ServerDailyStats> getDailyStatsByServer(int days) {
        LocalDateTime endDate = LocalDate.now().plusDays(1).atStartOfDay(); // 내일 00:00
        LocalDateTime startDate = LocalDate.now().minusDays(days - 1).atStartOfDay(); // N일 전 00:00
        
        // epoch milliseconds로 변환
        long startMs = startDate.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
        long endMs = endDate.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
        
        List<Object[]> results = errorLogRepository.getDailyStatsByServer(startMs, endMs);
        
        // 서버별로 그룹화
        Map<Long, ServerDailyStats> serverMap = new LinkedHashMap<>();
        
        for (Object[] row : results) {
            Long serverId = ((Number) row[0]).longValue();
            String serverName = (String) row[1];
            String dateStr = (String) row[2];  // SQLite date() 함수는 String 반환
            int total = ((Number) row[3]).intValue();
            int critical = ((Number) row[4]).intValue();
            int error = ((Number) row[5]).intValue();
            int warn = ((Number) row[6]).intValue();
            
            serverMap.computeIfAbsent(serverId, k -> new ServerDailyStats(serverId, serverName, new ArrayList<>()))
                    .dailyStats().add(new DailyStat(dateStr, total, critical, error, warn));
        }
        
        // 모든 날짜 채우기 (데이터 없는 날짜는 0으로)
        List<String> allDates = new ArrayList<>();
        for (int i = days - 1; i >= 0; i--) {
            allDates.add(LocalDate.now().minusDays(i).toString());
        }
        
        for (ServerDailyStats server : serverMap.values()) {
            Map<String, DailyStat> dateMap = server.dailyStats().stream()
                    .collect(Collectors.toMap(DailyStat::date, s -> s));
            
            List<DailyStat> filledStats = allDates.stream()
                    .map(date -> dateMap.getOrDefault(date, new DailyStat(date, 0, 0, 0, 0)))
                    .toList();
            
            server.dailyStats().clear();
            server.dailyStats().addAll(filledStats);
        }
        
        return new ArrayList<>(serverMap.values());
    }

    /**
     * 월별현황용: 서버별 해당 월 일별 통계
     */
    public List<ServerDailyStats> getMonthlyStatsByServer(int year, int month) {
        LocalDate firstDay = LocalDate.of(year, month, 1);
        LocalDate lastDay = firstDay.withDayOfMonth(firstDay.lengthOfMonth());
        
        LocalDateTime startDate = firstDay.atStartOfDay();
        LocalDateTime endDate = lastDay.plusDays(1).atStartOfDay();
        
        // epoch milliseconds로 변환
        long startMs = startDate.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
        long endMs = endDate.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
        
        log.info("Monthly stats query: year={}, month={}, startMs={}, endMs={}", year, month, startMs, endMs);
        
        List<Object[]> results = errorLogRepository.getDailyStatsByServer(startMs, endMs);
        
        log.info("Monthly stats results count: {}", results.size());
        
        // 서버별로 그룹화
        Map<Long, ServerDailyStats> serverMap = new LinkedHashMap<>();
        
        for (Object[] row : results) {
            Long serverId = ((Number) row[0]).longValue();
            String serverName = (String) row[1];
            String dateStr = (String) row[2];  // SQLite date() 함수는 String 반환
            int total = ((Number) row[3]).intValue();
            int critical = ((Number) row[4]).intValue();
            int error = ((Number) row[5]).intValue();
            int warn = ((Number) row[6]).intValue();
            
            log.debug("Row: serverId={}, serverName={}, date={}, total={}", serverId, serverName, dateStr, total);
            
            serverMap.computeIfAbsent(serverId, k -> new ServerDailyStats(serverId, serverName, new ArrayList<>()))
                    .dailyStats().add(new DailyStat(dateStr, total, critical, error, warn));
        }
        
        // 모든 날짜 채우기
        List<String> allDates = new ArrayList<>();
        for (int day = 1; day <= firstDay.lengthOfMonth(); day++) {
            allDates.add(LocalDate.of(year, month, day).toString());
        }
        
        for (ServerDailyStats server : serverMap.values()) {
            Map<String, DailyStat> dateMap = server.dailyStats().stream()
                    .collect(Collectors.toMap(DailyStat::date, s -> s));
            
            List<DailyStat> filledStats = allDates.stream()
                    .map(date -> dateMap.getOrDefault(date, new DailyStat(date, 0, 0, 0, 0)))
                    .toList();
            
            server.dailyStats().clear();
            server.dailyStats().addAll(filledStats);
        }
        
        return new ArrayList<>(serverMap.values());
    }

    /**
     * 일별현황용: 서버별 해당 날짜 5분 단위 통계
     */
    public List<ServerTimeStats> getTimeStatsByServer(LocalDate date, int intervalMinutes) {
        LocalDateTime startDate = date.atStartOfDay();
        LocalDateTime endDate = date.plusDays(1).atStartOfDay();
        
        List<Object[]> results = errorLogRepository.getErrorsByDateRange(startDate, endDate);
        
        // 서버별로 그룹화
        Map<Long, ServerTimeStats> serverMap = new LinkedHashMap<>();
        
        // 시간 슬롯 초기화 (5분 단위 = 288개)
        int slots = 24 * 60 / intervalMinutes;
        
        for (Object[] row : results) {
            Long serverId = ((Number) row[0]).longValue();
            String serverName = (String) row[1];
            LocalDateTime occurredAt = (LocalDateTime) row[2];
            String severity = (String) row[3];
            
            ServerTimeStats stats = serverMap.computeIfAbsent(serverId, 
                    k -> new ServerTimeStats(serverId, serverName, initTimeSlots(slots, intervalMinutes)));
            
            // 해당 시간 슬롯 찾기
            int minuteOfDay = occurredAt.getHour() * 60 + occurredAt.getMinute();
            int slotIndex = minuteOfDay / intervalMinutes;
            
            if (slotIndex < stats.timeStats().size()) {
                TimeStat slot = stats.timeStats().get(slotIndex);
                slot.incrementTotal();
                switch (severity) {
                    case "CRITICAL" -> slot.incrementCritical();
                    case "ERROR" -> slot.incrementError();
                    case "WARN" -> slot.incrementWarn();
                }
            }
        }
        
        return new ArrayList<>(serverMap.values());
    }
    
    private List<TimeStat> initTimeSlots(int slots, int intervalMinutes) {
        List<TimeStat> timeStats = new ArrayList<>();
        for (int i = 0; i < slots; i++) {
            int minutes = i * intervalMinutes;
            String time = String.format("%02d:%02d", minutes / 60, minutes % 60);
            timeStats.add(new TimeStat(time));
        }
        return timeStats;
    }

    // DTOs for stats
    public record ResetResult(int deletedErrors, int deletedFiles, int deletedHistories) {}
    
    public record FileErrorStats(
            String filePath,
            String serverName,
            int totalCount,
            int criticalCount,
            int errorCount,
            int warnCount,
            LocalDateTime lastOccurredAt
    ) {}
    
    public record ServerErrorStats(
            Long serverId,
            String serverName,
            int totalCount,
            int criticalCount,
            int errorCount,
            int warnCount,
            LocalDateTime lastOccurredAt
    ) {}
    
    public record PatternErrorStats(
            Long patternId,
            String patternName,
            String severity,
            int count,
            LocalDateTime lastOccurredAt
    ) {}
    
    // 일별 통계 DTO
    public record ServerDailyStats(
            Long serverId,
            String serverName,
            List<DailyStat> dailyStats
    ) {}
    
    public record DailyStat(
            String date,
            int total,
            int critical,
            int error,
            int warn
    ) {}
    
    // 시간별 통계 DTO
    public record ServerTimeStats(
            Long serverId,
            String serverName,
            List<TimeStat> timeStats
    ) {}
    
    public static class TimeStat {
        private final String time;
        private int total;
        private int critical;
        private int error;
        private int warn;
        
        public TimeStat(String time) {
            this.time = time;
        }
        
        public String getTime() { return time; }
        public int getTotal() { return total; }
        public int getCritical() { return critical; }
        public int getError() { return error; }
        public int getWarn() { return warn; }
        
        public void incrementTotal() { total++; }
        public void incrementCritical() { critical++; }
        public void incrementError() { error++; }
        public void incrementWarn() { warn++; }
    }
}
