package research.loghunter.service;

import com.jcraft.jsch.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import research.loghunter.entity.Server;
import research.loghunter.entity.ServerLogPath;
import research.loghunter.repository.ServerRepository;
import research.loghunter.util.CryptoUtil;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.regex.Pattern;

@Service
@RequiredArgsConstructor
@Slf4j
public class SftpService {

    private final ServerRepository serverRepository;
    private final CryptoUtil cryptoUtil;

    private static final int CONNECT_TIMEOUT = 10000;  // 10초
    private static final int CHANNEL_TIMEOUT = 30000;  // 30초

    /**
     * SFTP 연결 테스트
     */
    public ConnectionTestResult testConnection(Long serverId) {
        Server server = serverRepository.findById(serverId)
                .orElseThrow(() -> new RuntimeException("Server not found: " + serverId));

        Session session = null;
        ChannelSftp channel = null;
        
        try {
            session = createSession(server);
            
            log.info("Connecting to {}:{} as user '{}' with auth type '{}'", 
                    server.getHost(), server.getPort(), server.getUsername(), server.getAuthType());
            
            session.connect(CONNECT_TIMEOUT);
            
            channel = (ChannelSftp) session.openChannel("sftp");
            channel.connect(CHANNEL_TIMEOUT);
            
            // 홈 디렉토리 확인
            String home = channel.pwd();
            
            String detail = String.format("연결 성공!\n- 호스트: %s:%d\n- 사용자: %s\n- 인증방식: %s\n- 홈 디렉토리: %s",
                    server.getHost(), server.getPort(), server.getUsername(),
                    "PASSWORD".equals(server.getAuthType()) ? "비밀번호" : "키 파일",
                    home);
            
            return new ConnectionTestResult(true, detail, null);
        } catch (JSchException e) {
            log.error("SFTP connection test failed for server {}: {}", serverId, e.getMessage(), e);
            String errorDetail = analyzeJSchException(e, server);
            return new ConnectionTestResult(false, null, errorDetail);
        } catch (SftpException e) {
            log.error("SFTP operation failed for server {}: {}", serverId, e.getMessage());
            return new ConnectionTestResult(false, null, "SFTP 오류: " + e.getMessage());
        } finally {
            disconnect(channel, session);
        }
    }

    /**
     * 서버 시간 조회 및 로컬 시간과의 차이 계산 (분 단위)
     * 반환값: 로그시간 + offset = 로컬시간
     */
    public TimeCheckResult checkServerTime(Long serverId) {
        Server server = serverRepository.findById(serverId)
                .orElseThrow(() -> new RuntimeException("Server not found: " + serverId));
        
        return checkServerTime(server);
    }
    
    public TimeCheckResult checkServerTime(Server server) {
        Session session = null;
        ChannelExec channel = null;
        
        try {
            session = createSession(server);
            session.connect(CONNECT_TIMEOUT);
            
            // 서버 시간 조회 (epoch seconds)
            channel = (ChannelExec) session.openChannel("exec");
            channel.setCommand("date +%s");
            
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            channel.setOutputStream(outputStream);
            channel.connect(CHANNEL_TIMEOUT);
            
            // 명령 실행 대기
            while (!channel.isClosed()) {
                Thread.sleep(100);
            }
            
            String serverTimeStr = outputStream.toString().trim();
            long serverEpoch = Long.parseLong(serverTimeStr);
            long localEpoch = Instant.now().getEpochSecond();
            
            // 분 단위 차이 계산 (로컬 - 서버)
            int offsetMinutes = (int) ((localEpoch - serverEpoch) / 60);
            
            LocalDateTime serverTime = LocalDateTime.ofInstant(
                    Instant.ofEpochSecond(serverEpoch), ZoneId.systemDefault());
            LocalDateTime localTime = LocalDateTime.now();
            
            log.info("Server {} time check - Server: {}, Local: {}, Offset: {} minutes",
                    server.getId(), serverTime, localTime, offsetMinutes);
            
            return new TimeCheckResult(true, serverTime, localTime, offsetMinutes, null);
            
        } catch (Exception e) {
            log.error("Failed to check server time for {}: {}", server.getId(), e.getMessage());
            return new TimeCheckResult(false, null, LocalDateTime.now(), 0, e.getMessage());
        } finally {
            if (channel != null && channel.isConnected()) {
                channel.disconnect();
            }
            if (session != null && session.isConnected()) {
                session.disconnect();
            }
        }
    }

    /**
     * JSchException 분석하여 상세 에러 메시지 생성
     */
    private String analyzeJSchException(JSchException e, Server server) {
        String msg = e.getMessage();
        StringBuilder detail = new StringBuilder();
        
        detail.append(String.format("대상 서버: %s:%d\n", server.getHost(), server.getPort()));
        detail.append(String.format("사용자: %s\n", server.getUsername()));
        detail.append(String.format("인증방식: %s\n\n", 
                "PASSWORD".equals(server.getAuthType()) ? "비밀번호" : "키 파일"));
        
        if (msg.contains("Auth fail") || msg.contains("Auth cancel")) {
            detail.append("❌ 인증 실패\n\n");
            detail.append("✔ IP/포트 연결은 성공했습니다.\n\n");
            detail.append("확인 사항:\n");
            if ("PASSWORD".equals(server.getAuthType())) {
                detail.append("- 사용자명이 정확한가요?\n");
                detail.append("- 비밀번호가 정확한가요?\n");
                detail.append("- 서버에서 비밀번호 인증이 허용되어 있나요?");
            } else {
                detail.append("- 키 파일 경로가 정확한가요? (").append(server.getKeyFilePath()).append(")\n");
                detail.append("- 키 파일이 존재하나요?\n");
                detail.append("- Passphrase가 필요한 경우 입력했나요?\n");
                detail.append("- 서버에 공개키가 등록되어 있나요?");
            }
        } else if (msg.contains("Connection refused")) {
            detail.append("❌ 연결 거부\n\n");
            detail.append("확인 사항:\n");
            detail.append("- 포트 번호가 정확한가요? (현재: ").append(server.getPort()).append(")\n");
            detail.append("- 서버에서 SSH 서비스가 실행 중인가요?\n");
            detail.append("- 방화벽에서 해당 포트가 열려 있나요?");
        } else if (msg.contains("UnknownHost") || msg.contains("No route to host")) {
            detail.append("❌ 호스트 연결 불가\n\n");
            detail.append("확인 사항:\n");
            detail.append("- IP 주소가 정확한가요? (현재: ").append(server.getHost()).append(")\n");
            detail.append("- 서버가 실행 중인가요?\n");
            detail.append("- 네트워크 연결이 가능한가요?");
        } else if (msg.contains("timeout") || msg.contains("timed out")) {
            detail.append("❌ 연결 시간 초과\n\n");
            detail.append("확인 사항:\n");
            detail.append("- IP 주소가 정확한가요?\n");
            detail.append("- 방화벽에서 차단되어 있지 않나요?\n");
            detail.append("- 서버가 응답하는지 확인해주세요.");
        } else if (msg.contains("invalid privatekey")) {
            detail.append("❌ 키 파일 오류\n\n");
            detail.append("확인 사항:\n");
            detail.append("- 키 파일 형식이 올바른가요? (OpenSSH 형식 필요)\n");
            detail.append("- 키 파일이 손상되지 않았나요?");
        } else {
            detail.append("❌ 연결 실패\n\n");
            detail.append("에러 메시지: ").append(msg);
        }
        
        return detail.toString();
    }

    // 분석 대상 파일 최대 기간 (1개월)
    private static final int MAX_FILE_AGE_DAYS = 31;

    /**
     * 지정된 경로의 모든 파일 목록 조회 (패턴 매칭 포함, 1개월 이내 파일만)
     */
    public List<RemoteFile> listAllFiles(Server server, ServerLogPath logPath) {
        List<RemoteFile> files = new ArrayList<>();
        Session session = null;
        ChannelSftp channel = null;

        // 1개월 전 기준 시간
        LocalDateTime cutoffTime = LocalDateTime.now().minusDays(MAX_FILE_AGE_DAYS);

        try {
            session = createSession(server);
            session.connect(CONNECT_TIMEOUT);
            
            channel = (ChannelSftp) session.openChannel("sftp");
            channel.connect(CHANNEL_TIMEOUT);

            String path = logPath.getPath();
            String filePattern = logPath.getFilePattern();
            Pattern pattern = convertGlobToRegex(filePattern);

            @SuppressWarnings("unchecked")
            Vector<ChannelSftp.LsEntry> entries = channel.ls(path);
            
            int skippedOldFiles = 0;
            for (ChannelSftp.LsEntry entry : entries) {
                if (entry.getAttrs().isDir()) continue;
                
                String fileName = entry.getFilename();
                if (!pattern.matcher(fileName).matches()) continue;

                long mtime = entry.getAttrs().getMTime() * 1000L;
                LocalDateTime fileTime = LocalDateTime.ofInstant(
                        Instant.ofEpochMilli(mtime), ZoneId.systemDefault());

                // 1개월 이전 파일은 제외
                if (fileTime.isBefore(cutoffTime)) {
                    skippedOldFiles++;
                    continue;
                }

                files.add(new RemoteFile(
                        path + (path.endsWith("/") ? "" : "/") + fileName,
                        fileName,
                        entry.getAttrs().getSize(),
                        fileTime
                ));
            }

            // 최신 파일 순 정렬
            files.sort((a, b) -> b.modifiedAt().compareTo(a.modifiedAt()));
            
            log.info("Found {} files (skipped {} old files > {} days) matching pattern '{}' in path '{}' on server {}", 
                    files.size(), skippedOldFiles, MAX_FILE_AGE_DAYS, filePattern, path, server.getId());
            
        } catch (Exception e) {
            log.error("Failed to list files from server {}: {}", server.getId(), e.getMessage());
            throw new RuntimeException("파일 목록 조회 실패: " + e.getMessage(), e);
        } finally {
            disconnect(channel, session);
        }

        return files;
    }

    /**
     * 로그 파일 목록 조회 (since 이후 수정된 파일만)
     */
    public List<RemoteFile> listLogFiles(Server server, ServerLogPath logPath, LocalDateTime since) {
        List<RemoteFile> allFiles = listAllFiles(server, logPath);
        
        if (since == null) {
            return allFiles;
        }
        
        return allFiles.stream()
                .filter(f -> f.modifiedAt().isAfter(since))
                .toList();
    }

    /**
     * 파일 다운로드 (내용 반환)
     */
    public String downloadFileContent(Server server, String remotePath) {
        Session session = null;
        ChannelSftp channel = null;

        try {
            session = createSession(server);
            session.connect(CONNECT_TIMEOUT);
            
            channel = (ChannelSftp) session.openChannel("sftp");
            channel.connect(CHANNEL_TIMEOUT);

            try (InputStream is = channel.get(remotePath);
                 BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
                
                StringBuilder content = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    content.append(line).append("\n");
                }
                return content.toString();
            }
            
        } catch (Exception e) {
            log.error("Failed to download file {} from server {}: {}", 
                    remotePath, server.getId(), e.getMessage());
            throw new RuntimeException("파일 다운로드 실패: " + e.getMessage(), e);
        } finally {
            disconnect(channel, session);
        }
    }

    /**
     * 파일 다운로드 (로컬 파일로)
     */
    public Path downloadFileToLocal(Server server, String remotePath, Path localDir) {
        Session session = null;
        ChannelSftp channel = null;

        try {
            session = createSession(server);
            session.connect(CONNECT_TIMEOUT);
            
            channel = (ChannelSftp) session.openChannel("sftp");
            channel.connect(CHANNEL_TIMEOUT);

            String fileName = remotePath.substring(remotePath.lastIndexOf('/') + 1);
            Path localPath = localDir.resolve(fileName);
            
            Files.createDirectories(localDir);
            
            try (OutputStream os = Files.newOutputStream(localPath)) {
                channel.get(remotePath, os);
            }
            
            return localPath;
            
        } catch (Exception e) {
            log.error("Failed to download file {} from server {}: {}", 
                    remotePath, server.getId(), e.getMessage());
            throw new RuntimeException("파일 다운로드 실패: " + e.getMessage(), e);
        } finally {
            disconnect(channel, session);
        }
    }

    /**
     * 세션 생성
     */
    private Session createSession(Server server) throws JSchException {
        JSch jsch = new JSch();
        
        if ("KEY_FILE".equals(server.getAuthType())) {
            String passphrase = cryptoUtil.decrypt(server.getEncryptedPassphrase());
            if (passphrase != null && !passphrase.isEmpty()) {
                jsch.addIdentity(server.getKeyFilePath(), passphrase);
            } else {
                jsch.addIdentity(server.getKeyFilePath());
            }
        }

        Session session = jsch.getSession(server.getUsername(), server.getHost(), server.getPort());
        
        if ("PASSWORD".equals(server.getAuthType())) {
            String password = cryptoUtil.decrypt(server.getEncryptedPassword());
            session.setPassword(password);
        }

        // StrictHostKeyChecking 비활성화 (실제 운영에서는 known_hosts 사용 권장)
        Properties config = new Properties();
        config.put("StrictHostKeyChecking", "no");
        session.setConfig(config);

        return session;
    }

    /**
     * 연결 해제
     */
    private void disconnect(ChannelSftp channel, Session session) {
        if (channel != null && channel.isConnected()) {
            channel.disconnect();
        }
        if (session != null && session.isConnected()) {
            session.disconnect();
        }
    }

    /**
     * Glob 패턴을 정규식으로 변환
     */
    private Pattern convertGlobToRegex(String glob) {
        StringBuilder regex = new StringBuilder("^");
        for (char c : glob.toCharArray()) {
            switch (c) {
                case '*': regex.append(".*"); break;
                case '?': regex.append("."); break;
                case '.': regex.append("\\."); break;
                default: regex.append(c);
            }
        }
        regex.append("$");
        return Pattern.compile(regex.toString());
    }

    // DTOs
    public record ConnectionTestResult(boolean success, String message, String error) {}
    
    public record TimeCheckResult(
            boolean success, 
            LocalDateTime serverTime, 
            LocalDateTime localTime, 
            int offsetMinutes, 
            String error
    ) {}
    
    public record RemoteFile(String path, String name, long size, LocalDateTime modifiedAt) {}
}
