package research.loghunter.service;

import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import research.loghunter.dto.ErrorLogDto;
import research.loghunter.dto.FileTreeDto;
import research.loghunter.entity.ErrorLog;
import research.loghunter.entity.Server;
import research.loghunter.repository.ErrorLogRepository;
import research.loghunter.repository.ScannedFileRepository;
import research.loghunter.repository.ServerRepository;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ErrorLogService {

    private final ErrorLogRepository errorLogRepository;
    private final ScannedFileRepository scannedFileRepository;
    private final ServerRepository serverRepository;

    public Page<ErrorLogDto> search(
            Long serverId,
            Long patternId,
            String severity,
            String filePath,
            LocalDateTime startDate,
            LocalDateTime endDate,
            String keyword,
            int page,
            int size
    ) {
        Pageable pageable = PageRequest.of(page, size);
        Page<ErrorLog> errorLogs = errorLogRepository.searchErrors(
                serverId, patternId, severity, filePath, startDate, endDate, keyword, pageable);
        
        return errorLogs.map(this::toDto);
    }

    public Page<ErrorLogDto> findByServerId(Long serverId, int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        Page<ErrorLog> errorLogs = errorLogRepository.findByServerIdOrderByOccurredAtDesc(serverId, pageable);
        return errorLogs.map(this::toDto);
    }

    public ErrorLogDto findById(Long id) {
        return errorLogRepository.findById(id)
                .map(this::toDto)
                .orElseThrow(() -> new RuntimeException("ErrorLog not found: " + id));
    }

    /**
     * 트리 구조 데이터 조회
     */
    public List<FileTreeDto.ServerNode> getFileTree() {
        // 파일별 에러 통계 조회
        List<Object[]> stats = errorLogRepository.getFileErrorStats();
        
        // 서버별로 그룹핑
        Map<Long, List<Object[]>> serverGroups = new LinkedHashMap<>();
        for (Object[] stat : stats) {
            Long serverId = ((Number) stat[0]).longValue();
            serverGroups.computeIfAbsent(serverId, k -> new ArrayList<>()).add(stat);
        }
        
        List<FileTreeDto.ServerNode> result = new ArrayList<>();
        
        for (Map.Entry<Long, List<Object[]>> entry : serverGroups.entrySet()) {
            Long serverId = entry.getKey();
            List<Object[]> serverStats = entry.getValue();
            
            String serverName = (String) serverStats.get(0)[1];
            
            // 경로별로 그룹핑
            Map<String, List<FileTreeDto.FileNode>> pathGroups = new LinkedHashMap<>();
            int serverTotalErrors = 0;
            
            for (Object[] stat : serverStats) {
                String filePath = (String) stat[2];
                int errorCount = ((Number) stat[3]).intValue();
                int criticalCount = ((Number) stat[4]).intValue();
                int errorLevelCount = ((Number) stat[5]).intValue();
                int warnCount = ((Number) stat[6]).intValue();
                
                // 경로와 파일명 분리
                int lastSlash = filePath.lastIndexOf('/');
                String path = lastSlash > 0 ? filePath.substring(0, lastSlash) : "/";
                String fileName = lastSlash > 0 ? filePath.substring(lastSlash + 1) : filePath;
                
                FileTreeDto.FileNode fileNode = FileTreeDto.FileNode.builder()
                        .filePath(filePath)
                        .fileName(fileName)
                        .errorCount(errorCount)
                        .criticalCount(criticalCount)
                        .errorLevelCount(errorLevelCount)
                        .warnCount(warnCount)
                        .build();
                
                pathGroups.computeIfAbsent(path, k -> new ArrayList<>()).add(fileNode);
                serverTotalErrors += errorCount;
            }
            
            // PathNode 생성
            List<FileTreeDto.PathNode> pathNodes = new ArrayList<>();
            for (Map.Entry<String, List<FileTreeDto.FileNode>> pathEntry : pathGroups.entrySet()) {
                List<FileTreeDto.FileNode> files = pathEntry.getValue();
                int pathTotalErrors = files.stream().mapToInt(FileTreeDto.FileNode::getErrorCount).sum();
                
                pathNodes.add(FileTreeDto.PathNode.builder()
                        .path(pathEntry.getKey())
                        .totalErrorCount(pathTotalErrors)
                        .files(files)
                        .build());
            }
            
            result.add(FileTreeDto.ServerNode.builder()
                    .serverId(serverId)
                    .serverName(serverName)
                    .totalErrorCount(serverTotalErrors)
                    .paths(pathNodes)
                    .build());
        }
        
        return result;
    }

    /**
     * 서버별 파일 목록 조회
     */
    public List<String> getFilesByServer(Long serverId) {
        if (serverId == null) {
            return errorLogRepository.findDistinctFilePaths();
        }
        return errorLogRepository.findDistinctFilePathsByServerId(serverId);
    }

    /**
     * 선택한 ID들의 에러 삭제
     */
    @Transactional
    public int deleteByIds(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return 0;
        }
        return errorLogRepository.deleteByIdIn(ids);
    }

    /**
     * 파일 삭제 (에러로그 + 스캔기록)
     */
    @Transactional
    public Map<String, Object> deleteFileAndErrors(Long serverId, String filePath) {
        int deletedErrors = errorLogRepository.deleteByServerIdAndFilePath(serverId, filePath);
        int deletedFiles = scannedFileRepository.deleteByServerIdAndFilePath(serverId, filePath);
        
        return Map.of(
                "success", true,
                "deletedErrors", deletedErrors,
                "deletedScannedFiles", deletedFiles
        );
    }

    /**
     * 서버+파일 기준 에러 삭제
     */
    @Transactional
    public int deleteByServerAndFile(Long serverId, String filePath) {
        return errorLogRepository.deleteByServerIdAndFilePath(serverId, filePath);
    }

    private ErrorLogDto toDto(ErrorLog errorLog) {
        return ErrorLogDto.builder()
                .id(errorLog.getId())
                .serverId(errorLog.getServer().getId())
                .serverName(errorLog.getServer().getName())
                .patternId(errorLog.getPattern().getId())
                .patternName(errorLog.getPattern().getName())
                .filePath(errorLog.getFilePath())
                .lineNumber(errorLog.getLineNumber())
                .summary(errorLog.getSummary())
                .context(errorLog.getContext())
                .severity(errorLog.getSeverity())
                .occurredAt(errorLog.getOccurredAt())
                .createdAt(errorLog.getCreatedAt())
                .build();
    }
}
