package com.qqchen.deploy.backend.deploy.service.impl;

import com.qqchen.deploy.backend.deploy.dto.ServerDTO;
import com.qqchen.deploy.backend.deploy.dto.ServerInfoDTO;
import com.qqchen.deploy.backend.deploy.dto.ServerInitializeDTO;
import com.qqchen.deploy.backend.deploy.entity.Server;
import com.qqchen.deploy.backend.deploy.enums.ServerStatusEnum;
import com.qqchen.deploy.backend.framework.enums.AuthTypeEnum;
import com.qqchen.deploy.backend.deploy.query.ServerQuery;
import com.qqchen.deploy.backend.deploy.repository.IServerRepository;
import com.qqchen.deploy.backend.deploy.repository.IServerMonitorRepository;
import com.qqchen.deploy.backend.deploy.repository.IServerAlertRuleRepository;
import com.qqchen.deploy.backend.deploy.repository.IServerAlertLogRepository;
import com.qqchen.deploy.backend.deploy.repository.ISSHAuditLogRepository;
import com.qqchen.deploy.backend.deploy.service.IServerService;
import com.qqchen.deploy.backend.framework.annotation.ServiceType;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import com.qqchen.deploy.backend.framework.ssh.ISSHCommandService;
import com.qqchen.deploy.backend.framework.ssh.SSHCommandServiceFactory;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import net.schmizz.sshj.SSHClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;


/**
 * 服务器服务实现
 */
@Slf4j
@Service
@ServiceType(ServiceType.Type.DATABASE)
public class ServerServiceImpl
    extends BaseServiceImpl<Server, ServerDTO, ServerQuery, Long>
    implements IServerService {

    private final IServerRepository serverRepository;
    
    @Resource
    private SSHCommandServiceFactory sshCommandServiceFactory;
    
    @Resource
    private ISSHAuditLogRepository sshAuditLogRepository;
    
    @Resource
    private IServerMonitorRepository serverMonitorRepository;
    
    @Resource
    private IServerAlertRuleRepository serverAlertRuleRepository;
    
    @Resource
    private IServerAlertLogRepository serverAlertLogRepository;

    public ServerServiceImpl(IServerRepository serverRepository) {
        this.serverRepository = serverRepository;
    }

    /**
     * 校验服务器唯一约束
     * 检查IP是否已存在
     */
    @Override
    protected void validateUniqueConstraints(ServerDTO dto) {
        // 检查IP是否已存在（排除已删除的记录）
        if (dto.getHostIp() != null) {
            boolean exists = serverRepository.existsByHostIpAndDeletedFalse(dto.getHostIp());
            if (exists) {
                log.warn("服务器IP已存在: {}", dto.getHostIp());
                throw new BusinessException(ResponseCode.SERVER_IP_EXISTS, new Object[]{dto.getHostIp()});
            }
        }
    }

    @Override
    @Transactional
    public ServerDTO initializeServerInfo(Long serverId, ServerInitializeDTO dto) {
        // 1. 查询服务器
        Server server = serverRepository.findById(serverId)
            .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND));
        
        // 2. 更新硬件信息
        server.setCpuCores(dto.getCpuCores());
        server.setMemorySize(dto.getMemorySize());
        server.setDiskSize(dto.getDiskSize());
        server.setDiskInfo(dto.getDiskInfo());
        server.setOsVersion(dto.getOsVersion());
        server.setHostname(dto.getHostname());
        server.setStatus(ServerStatusEnum.ONLINE);
        server.setLastConnectTime(LocalDateTime.now());
        
        // 3. 保存并返回
        Server updated = serverRepository.save(server);
        log.info("服务器初始化成功: serverId={}, cpuCores={}, memorySize={}GB, diskSize={}GB, diskCount={}", 
            serverId, dto.getCpuCores(), dto.getMemorySize(), dto.getDiskSize(), 
            dto.getDiskInfo() != null ? dto.getDiskInfo().size() : 0);
        
        return converter.toDto(updated);
    }

    /**
     * 创建SSH连接的通用方法（封装重复逻辑）
     * 
     * @param serverId 服务器ID
     * @return SSH连接信息 {server, sshClient, sshService}
     */
    private SSHConnectionContext createSSHConnection(Long serverId) throws Exception {
        // 1. 查询服务器信息
        Server server = serverRepository.findById(serverId)
            .orElseThrow(() -> new BusinessException(ResponseCode.DATA_NOT_FOUND));
        
        // 2. 验证必要字段
        if (server.getHostIp() == null || server.getSshUser() == null) {
            throw new BusinessException(ResponseCode.INVALID_PARAM, 
                new Object[]{"服务器IP和SSH用户名不能为空"});
        }
        
        if (server.getOsType() == null) {
            throw new BusinessException(ResponseCode.INVALID_PARAM,
                new Object[]{"请先选择操作系统类型"});
        }
        
        if (server.getAuthType() == null) {
            throw new BusinessException(ResponseCode.INVALID_PARAM,
                new Object[]{"请选择认证方式"});
        }
        
        // 3. 获取对应OS的SSH命令服务
        ISSHCommandService sshService = sshCommandServiceFactory.getService(server.getOsType());
        
        // 4. 根据认证类型准备参数
        String password = null;
        String privateKey = null;
        String passphrase = null;
        
        switch (server.getAuthType()) {
            case PASSWORD:
                if (server.getSshPassword() == null || server.getSshPassword().isEmpty()) {
                    throw new BusinessException(ResponseCode.INVALID_PARAM,
                        new Object[]{"SSH密码不能为空"});
                }
                password = server.getSshPassword();
                break;
                
            case KEY:
                if (server.getSshPrivateKey() == null || server.getSshPrivateKey().isEmpty()) {
                    throw new BusinessException(ResponseCode.INVALID_PARAM,
                        new Object[]{"SSH私钥不能为空"});
                }
                privateKey = server.getSshPrivateKey();
                passphrase = server.getSshPassphrase();
                break;
                
            default:
                throw new BusinessException(ResponseCode.INVALID_PARAM,
                    new Object[]{"不支持的认证类型: " + server.getAuthType()});
        }
        
        // 5. 创建SSH连接
        SSHClient sshClient = sshService.createConnection(
            server.getHostIp(),
            server.getSshPort(),
            server.getSshUser(),
            password,
            privateKey,
            passphrase
        );
        
        log.debug("SSH连接已创建: serverId={}, {}@{}:{}", 
            serverId, server.getSshUser(), server.getHostIp(), server.getSshPort());
        
        return new SSHConnectionContext(server, sshClient, sshService);
    }
    
    /**
     * SSH连接上下文（封装连接相关信息）
     */
    private static class SSHConnectionContext {
        final Server server;
        final SSHClient sshClient;
        final ISSHCommandService sshService;
        
        SSHConnectionContext(Server server, SSHClient sshClient, ISSHCommandService sshService) {
            this.server = server;
            this.sshClient = sshClient;
            this.sshService = sshService;
        }
    }
    
    /**
     * 测试服务器SSH连接（轻量级，只验证连通性）
     * 
     * 性能优化：不采集硬件信息，减少SSH命令执行
     * 
     * @param serverId 服务器ID
     * @return 连接状态信息（只包含连通性、错误信息、响应时间）
     */
    @Override
    @Transactional
    public ServerInfoDTO testConnection(Long serverId) {
        long startTime = System.currentTimeMillis();
        ServerInfoDTO info = new ServerInfoDTO();
        SSHConnectionContext context = null;
        
        try {
            // 1. 创建SSH连接（使用通用方法 ✅）
            context = createSSHConnection(serverId);
            
            // 2. 连接成功（不采集硬件信息 ✅）
            info.setConnected(true);
            log.info("SSH连接测试成功: serverId={}", serverId);
            
            // 3. 更新服务器状态（只更新状态和连接时间）
            context.server.setStatus(ServerStatusEnum.ONLINE);
            context.server.setLastConnectTime(LocalDateTime.now());
            serverRepository.save(context.server);
            
        } catch (Exception e) {
            log.error("测试连接失败: serverId={}, error={}", serverId, e.getMessage(), e);
            
            // 连接失败，设置错误信息
            info.setConnected(false);
            info.setErrorMessage(e.getMessage());
            
            // 尝试更新服务器状态为离线
            try {
                Server server = serverRepository.findById(serverId).orElse(null);
                if (server != null) {
                    server.setStatus(ServerStatusEnum.OFFLINE);
                    serverRepository.save(server);
                }
            } catch (Exception ex) {
                log.warn("更新服务器离线状态失败: serverId={}", serverId, ex);
            }
            
        } finally {
            // 4. 关闭SSH连接
            if (context != null && context.sshService != null) {
                context.sshService.closeConnection(context.sshClient);
            }
            
            // 5. 设置响应时间
            info.setResponseTime(System.currentTimeMillis() - startTime);
            info.setConnectTime(LocalDateTime.now());
        }
        
        return info;
    }
    
    /**
     * 采集服务器硬件信息（需要先确保服务器在线）
     * 
     * 前置条件：服务器必须在线（建议先调用 testConnection）
     * 
     * @param serverId 服务器ID
     * @return 完整的硬件信息
     */
    @Override
    @Transactional
    public ServerInfoDTO collectHardwareInfo(Long serverId) {
        long startTime = System.currentTimeMillis();
        ServerInfoDTO info = new ServerInfoDTO();
        SSHConnectionContext context = null;
        
        try {
            // 1. 创建SSH连接（使用通用方法 ✅）
            context = createSSHConnection(serverId);
            
            // 2. 验证服务器状态（建议先在线）
            if (context.server.getStatus() == ServerStatusEnum.OFFLINE) {
                log.warn("服务器当前离线，建议先调用testConnection: serverId={}", serverId);
            }
            
            log.info("开始采集硬件信息: serverId={}", serverId);
            
            // 3. 采集硬件信息
            info.setConnected(true);
            info.setHostname(context.sshService.getHostname(context.sshClient));
            info.setOsVersion(context.sshService.getOsVersion(context.sshClient));
            info.setCpuCores(context.sshService.getCpuCores(context.sshClient));
            info.setMemorySize(context.sshService.getMemorySize(context.sshClient));
            info.setDiskInfo(context.sshService.getDiskInfo(context.sshClient));
            
            // 计算磁盘总容量
            if (info.getDiskInfo() != null && !info.getDiskInfo().isEmpty()) {
                int totalSize = info.getDiskInfo().stream()
                    .mapToInt(disk -> disk.getTotalSize().intValue())
                    .sum();
                info.setDiskSize(totalSize);
            }
            
            log.info("硬件信息采集成功: serverId={}, hostname={}, cpu={}核, mem={}GB, disk={}GB, diskCount={}", 
                serverId, info.getHostname(), info.getCpuCores(), info.getMemorySize(), 
                info.getDiskSize(), info.getDiskInfo() != null ? info.getDiskInfo().size() : 0);
            
            // 4. 更新数据库中的硬件信息
            context.server.setHostname(info.getHostname());
            context.server.setOsVersion(info.getOsVersion());
            context.server.setCpuCores(info.getCpuCores());
            context.server.setMemorySize(info.getMemorySize());
            context.server.setDiskSize(info.getDiskSize());
            context.server.setDiskInfo(info.getDiskInfo());
            serverRepository.save(context.server);
            
            log.info("硬件信息已更新到数据库: serverId={}", serverId);
            
        } catch (Exception e) {
            log.error("采集硬件信息失败: serverId={}, error={}", serverId, e.getMessage(), e);
            info.setConnected(false);
            info.setErrorMessage("采集硬件信息失败: " + e.getMessage());
            
        } finally {
            // 5. 关闭SSH连接
            if (context != null && context.sshService != null) {
                context.sshService.closeConnection(context.sshClient);
            }
            
            // 6. 设置响应时间
            info.setResponseTime(System.currentTimeMillis() - startTime);
            info.setConnectTime(LocalDateTime.now());
        }
        
        return info;
    }
    
    /**
     * 重写删除方法
     * 
     * ⚠️ 删除策略：
     * 1. 服务器：物理删除（没有@LogicDelete注解）
     * 2. 审计日志：逻辑删除（有@LogicDelete注解）
     * 3. 审计日志永久保留，仅标记deleted=true，确保审计可追溯
     * 4. 监控记录、告警规则、告警记录：物理删除（无需保留历史）
     */
    @Override
    @Transactional
    public void delete(Long id) {
        log.info("删除服务器: serverId={}", id);
        
        // 1. 物理删除关联的告警记录（需要先删除，因为它依赖告警规则）
        try {
            int alertLogCount = serverAlertLogRepository.deleteByServerId(id);
            log.info("已删除服务器关联的告警记录: serverId={}, count={}", id, alertLogCount);
        } catch (Exception e) {
            log.warn("删除服务器关联的告警记录失败: serverId={}, error={}", id, e.getMessage());
        }
        
        // 2. 物理删除关联的告警规则
        try {
            int alertRuleCount = serverAlertRuleRepository.deleteByServerId(id);
            log.info("已删除服务器关联的告警规则: serverId={}, count={}", id, alertRuleCount);
        } catch (Exception e) {
            log.warn("删除服务器关联的告警规则失败: serverId={}, error={}", id, e.getMessage());
        }
        
        // 3. 物理删除关联的监控记录
        try {
            int monitorCount = serverMonitorRepository.deleteByServerId(id);
            log.info("已删除服务器关联的监控记录: serverId={}, count={}", id, monitorCount);
        } catch (Exception e) {
            log.warn("删除服务器关联的监控记录失败: serverId={}, error={}", id, e.getMessage());
        }
        
        // 4. 逻辑删除关联的SSH审计日志（保留历史记录）
        try {
            sshAuditLogRepository.deleteByServerId(id);
            log.info("已逻辑删除服务器关联的SSH审计日志: serverId={}", id);
        } catch (Exception e) {
            log.warn("逻辑删除服务器关联的SSH审计日志失败: serverId={}, error={}", id, e.getMessage());
        }
        
        // 5. 物理删除服务器
        super.delete(id);
        log.info("服务器删除成功: serverId={}", id);
    }
}

