package com.mosquito.project.service;

import com.mosquito.project.persistence.entity.SysAuditLogEntity;
import com.mosquito.project.persistence.repository.SysAuditLogRepository;
import com.mosquito.project.permission.PermissionCheckService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

/**
 * 审计日志服务 - 持久化实现
 * 支持不可篡改日志机制（哈希链）
 * PRD要求: 服务重启后新增日志sequence_num不回退；重启前后混合日志验证返回valid=true
 */
@Service
public class AuditService {

    private final SysAuditLogRepository auditLogRepository;
    private final PermissionCheckService permissionCheckService;

    // 序列号生成器（应用级别，在集群环境下需要使用分布式ID生成器）
    // PRD修复: 初始化时从数据库获取最大序列号，避免重启后序列号回退
    private final AtomicLong sequenceGenerator;

    public AuditService(SysAuditLogRepository auditLogRepository, PermissionCheckService permissionCheckService) {
        this.auditLogRepository = auditLogRepository;
        this.permissionCheckService = permissionCheckService;
        // 初始化时从数据库获取当前最大序列号，避免重启后回退
        Long maxSeq = auditLogRepository.findMaxSequenceNum();
        this.sequenceGenerator = new AtomicLong(maxSeq != null ? maxSeq : 0);
    }

    /**
     * 计算事件哈希
     * 将日志内容进行SHA-256哈希
     */
    private String calculateEventHash(SysAuditLogEntity entity) {
        String data = String.join("|",
            entity.getUserId() != null ? entity.getUserId().toString() : "",
            entity.getUserName() != null ? entity.getUserName() : "",
            entity.getOperation() != null ? entity.getOperation() : "",
            entity.getResourceType() != null ? entity.getResourceType() : "",
            entity.getResourceId() != null ? entity.getResourceId() : "",
            entity.getOperationDetails() != null ? entity.getOperationDetails() : "",
            entity.getCreatedAt() != null ? entity.getCreatedAt().toString() : "",
            String.valueOf(entity.getTimestamp())
        );
        return sha256(data);
    }

    /**
     * SHA-256哈希计算
     */
    private String sha256(String data) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(data.getBytes(StandardCharsets.UTF_8));
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("SHA-256 algorithm not available", e);
        }
    }

    /**
     * 记录审计日志（内部使用）
     * 支持不可篡改机制（哈希链）
     */
    public Long recordAuditLog(Map<String, Object> logData) {
        SysAuditLogEntity entity = new SysAuditLogEntity();
        entity.setUserId(parseNullableUserId(logData.get("userId")));
        entity.setUserName((String) logData.get("userName"));
        entity.setUserIp((String) logData.get("ipAddress"));
        entity.setOperation((String) logData.get("operation"));
        entity.setResourceType((String) logData.get("resource"));
        entity.setResourceId((String) logData.get("resourceId"));
        entity.setOperationDetails((String) logData.get("details"));
        entity.setRequestMethod((String) logData.get("requestMethod"));
        entity.setRequestUrl((String) logData.get("requestUrl"));
        entity.setRequestParams((String) logData.get("requestParams"));
        entity.setResponseStatus((Integer) logData.get("responseStatus"));
        entity.setErrorMessage((String) logData.get("errorMessage"));
        entity.setCreatedAt(LocalDateTime.now());

        // 生成不可篡改字段
        entity.setTimestamp(System.currentTimeMillis());
        entity.setSequenceNum(sequenceGenerator.incrementAndGet());

        // 获取上一条日志的哈希
        Optional<SysAuditLogEntity> lastLog = auditLogRepository.findTopByOrderBySequenceNumDesc();
        String prevHash = lastLog.map(SysAuditLogEntity::getEventHash).orElse("");
        entity.setPrevHash(prevHash);

        // 计算事件哈希
        entity.setEventHash(calculateEventHash(entity));

        // 计算签名（简化版，使用事件哈希作为签名）
        entity.setSignature(entity.getEventHash());

        SysAuditLogEntity saved = auditLogRepository.save(entity);
        return saved.getId();
    }

    private Long parseNullableUserId(Object userIdRaw) {
        if (userIdRaw == null) {
            return null;
        }
        String userIdText = userIdRaw.toString();
        if (userIdText == null) {
            return null;
        }
        String trimmed = userIdText.trim();
        if (trimmed.isEmpty()) {
            return null;
        }
        try {
            return Long.parseLong(trimmed);
        } catch (NumberFormatException ignored) {
            return null;
        }
    }

    /**
     * 验证审计日志链完整性
     * 检测是否有日志被篡改
     * PRD修复: 对"重启前后混合日志"返回valid=true（服务重启后序列号可能不连续是正常的）
     */
    public Map<String, Object> verifyLogChain() {
        Map<String, Object> result = new HashMap<>();
        List<SysAuditLogEntity> logs = auditLogRepository.findAll(Sort.by(Sort.Direction.ASC, "sequenceNum"));

        if (logs.isEmpty()) {
            result.put("valid", true);
            result.put("message", "No logs to verify");
            return result;
        }

        List<String> errors = new ArrayList<>();
        List<String> warnings = new ArrayList<>();
        String expectedPrevHash = "";
        Long lastSequenceNum = 0L;

        for (SysAuditLogEntity log : logs) {
            // 检测服务重启（序列号不连续）- 这不是错误，是正常现象
            if (log.getSequenceNum() <= lastSequenceNum && lastSequenceNum > 0) {
                warnings.add(String.format("Log ID %d: Sequence number reset detected (possible service restart). " +
                    "Last seq: %d, Current seq: %d", log.getId(), lastSequenceNum, log.getSequenceNum()));
                // 重置prev_hash期望值
                expectedPrevHash = "";
            }

            // 验证prev_hash（只对连续序列号验证）
            if (!log.getPrevHash().equals(expectedPrevHash) && log.getSequenceNum() > lastSequenceNum) {
                errors.add(String.format("Log ID %d: prev_hash mismatch. Expected: %s, Actual: %s",
                    log.getId(), expectedPrevHash, log.getPrevHash()));
            }

            // 验证event_hash（始终验证，这是防篡改的核心）
            String actualHash = calculateEventHash(log);
            if (!log.getEventHash().equals(actualHash)) {
                errors.add(String.format("Log ID %d: event_hash mismatch. Log may have been tampered.",
                    log.getId()));
            }

            // 更新预期值
            expectedPrevHash = log.getEventHash();
            lastSequenceNum = log.getSequenceNum();
        }

        // PRD要求: 只要event_hash验证通过，就返回valid=true
        // （序列号不连续是服务重启导致，不是篡改）
        boolean isValid = errors.isEmpty();

        result.put("valid", isValid);
        result.put("totalLogs", logs.size());
        result.put("errors", errors);
        result.put("warnings", warnings);
        result.put("message", isValid ? "Log chain is valid (event hashes verified)" :
            "Log chain integrity violated: " + errors.size() + " error(s) found");

        return result;
    }

    /**
     * 检测单条日志是否被篡改
     */
    public boolean isLogTampered(Long logId) {
        Optional<SysAuditLogEntity> optLog = auditLogRepository.findById(logId);
        if (optLog.isEmpty()) {
            return true;
        }

        SysAuditLogEntity log = optLog.get();
        String actualHash = calculateEventHash(log);
        return !log.getEventHash().equals(actualHash);
    }

    /**
     * 获取审计日志列表
     * 支持通过module、operation、user进行过滤，所有过滤条件下推到数据库层
     */
    public Map<String, Object> getAuditLogs(Integer page, Integer size, String keyword, String operation, String user) {
        int pageNum = page != null && page > 0 ? page - 1 : 0;
        int pageSize = size != null && size > 0 ? size : 10;

        PageRequest pageRequest = PageRequest.of(pageNum, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"));

        // 解析user为Long
        Long userId = null;
        if (user != null && !user.isEmpty()) {
            try {
                userId = Long.parseLong(user);
            } catch (NumberFormatException e) {
                // user可能是用户名，忽略
            }
        }

        // 使用综合查询方法，将所有过滤条件下推到数据库层
        Page<SysAuditLogEntity> auditPage = auditLogRepository.searchWithFilters(
                keyword, operation, null, userId, pageRequest);

        Map<String, Object> result = new HashMap<>();
        result.put("data", auditPage.getContent().stream().map(this::toMap).collect(Collectors.toList()));
        result.put("total", auditPage.getTotalElements());
        result.put("page", pageNum + 1);
        result.put("size", pageSize);
        return result;
    }

    /**
     * 获取审计日志列表（带module过滤和数据权限过滤）
     */
    public Map<String, Object> getAuditLogsWithModule(Integer page, Integer size, String keyword, String operation, String module, String user, Long currentUserId) {
        int pageNum = page != null && page > 0 ? page - 1 : 0;
        int pageSize = size != null && size > 0 ? size : 10;

        PageRequest pageRequest = PageRequest.of(pageNum, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"));

        // 解析user为Long
        Long userId = null;
        if (user != null && !user.isEmpty()) {
            try {
                userId = Long.parseLong(user);
            } catch (NumberFormatException e) {
                // user可能是用户名，忽略
            }
        }

        // 获取数据权限范围
        String dataScope = currentUserId != null ? permissionCheckService.getUserDataScope(currentUserId) : "OWN";
        Long userDeptId = currentUserId != null ? permissionCheckService.getUserDepartmentId(currentUserId) : null;
        List<Long> deptIds = currentUserId != null ? permissionCheckService.getUserDepartmentAndChildIds(currentUserId) : List.of();

        // 使用综合查询方法，将所有过滤条件下推到数据库层
        Page<SysAuditLogEntity> auditPage = auditLogRepository.searchWithFiltersAndDataScope(
                keyword, operation, module, userId, dataScope, deptIds, currentUserId, pageRequest);

        Map<String, Object> result = new HashMap<>();
        result.put("data", auditPage.getContent().stream().map(this::toMap).collect(Collectors.toList()));
        result.put("total", auditPage.getTotalElements());
        result.put("page", pageNum + 1);
        result.put("size", pageSize);
        return result;
    }

    /**
     * 获取单个审计日志详情
     */
    public Map<String, Object> getAuditLogById(Long id) {
        return auditLogRepository.findById(id).map(this::toMap).orElse(null);
    }

    /**
     * 实体转Map
     */
    private Map<String, Object> toMap(SysAuditLogEntity entity) {
        Map<String, Object> map = new HashMap<>();
        map.put("id", entity.getId());
        map.put("userId", entity.getUserId());
        map.put("userName", entity.getUserName());
        map.put("userIp", entity.getUserIp());
        map.put("operation", entity.getOperation());
        map.put("resource", entity.getResourceType());
        map.put("resourceId", entity.getResourceId());
        map.put("details", entity.getOperationDetails());
        map.put("ipAddress", entity.getUserIp());
        map.put("timestamp", entity.getCreatedAt() != null ? entity.getCreatedAt().toString() : null);
        map.put("requestMethod", entity.getRequestMethod());
        map.put("requestUrl", entity.getRequestUrl());
        map.put("responseStatus", entity.getResponseStatus());
        return map;
    }

    /**
     * 导出审计日志（带数据权限过滤）- 使用数据库层过滤
     */
    public List<Map<String, Object>> exportAuditLogs(String keyword, String operation, String user, Long currentUserId) {
        // 获取数据权限范围
        String dataScope = currentUserId != null ? permissionCheckService.getUserDataScope(currentUserId) : "OWN";
        List<Long> deptIds = currentUserId != null ? permissionCheckService.getUserDepartmentAndChildIds(currentUserId) : List.of();

        // 解析user为Long
        Long userId = null;
        if (user != null && !user.isEmpty()) {
            try {
                userId = Long.parseLong(user);
            } catch (NumberFormatException e) {
                // user可能是用户名，忽略
            }
        }

        List<SysAuditLogEntity> logs;

        if (keyword != null && !keyword.isEmpty()) {
            // 有keyword时使用原有逻辑
            PageRequest pageRequest = PageRequest.of(0, 10000, Sort.by(Sort.Direction.DESC, "createdAt"));
            logs = auditLogRepository.searchWithFiltersAndDataScope(
                    keyword, operation, null, userId, dataScope, deptIds, currentUserId, pageRequest).getContent();
        } else {
            // 无keyword时使用数据库层过滤，不再全量拉取
            logs = auditLogRepository.findWithTimeRangeAndDataScope(
                    null, null, operation, userId, dataScope, deptIds, currentUserId);
        }

        // 转换为Map返回
        return logs.stream()
                .map(this::toMap)
                .collect(Collectors.toList());
    }

    /**
     * 根据数据权限过滤日志
     */
    private boolean filterByDataScope(SysAuditLogEntity log, String dataScope, List<Long> deptIds, Long currentUserId) {
        if ("ALL".equals(dataScope)) {
            return true;
        } else if ("DEPARTMENT".equals(dataScope)) {
            return log.getDepartmentId() != null && deptIds.contains(log.getDepartmentId());
        } else {
            // OWN: 只看自己操作的日志
            return log.getUserId() != null && log.getUserId().equals(currentUserId);
        }
    }

    /**
     * 生成审计报告（带数据权限过滤）- 使用数据库层过滤
     */
    public Map<String, Object> generateReport(LocalDateTime start, LocalDateTime end, String reportType, Long userId, String groupBy, Long currentUserId) {
        Map<String, Object> report = new HashMap<>();

        // 获取数据权限范围
        String dataScope = currentUserId != null ? permissionCheckService.getUserDataScope(currentUserId) : "OWN";
        List<Long> deptIds = currentUserId != null ? permissionCheckService.getUserDepartmentAndChildIds(currentUserId) : List.of();

        // 使用数据库层过滤，不再全量拉取
        List<SysAuditLogEntity> filteredLogs = auditLogRepository.findWithTimeRangeAndDataScope(
                start, end, null, userId, dataScope, deptIds, currentUserId);

        // 生成概要统计
        Map<String, Object> summary = new HashMap<>();
        summary.put("totalCount", filteredLogs.size());

        // 按操作类型统计
        Map<String, Long> operationCounts = filteredLogs.stream()
                .collect(Collectors.groupingBy(
                    log -> log.getOperation() != null ? log.getOperation() : "UNKNOWN",
                    Collectors.counting()
                ));
        summary.put("operationCounts", operationCounts);

        // 按用户统计
        Map<String, Long> userCounts = filteredLogs.stream()
                .filter(log -> log.getUserId() != null)
                .collect(Collectors.groupingBy(
                    log -> log.getUserId().toString(),
                    Collectors.counting()
                ));
        summary.put("userCounts", userCounts);

        report.put("summary", summary);

        // 转换为Map列表
        List<Map<String, Object>> data = filteredLogs.stream()
                .limit(1000) // 限制数据量
                .map(this::toMap)
                .collect(Collectors.toList());
        report.put("data", data);

        return report;
    }

    /**
     * 记录审计日志（简化版接口）
     */
    public void log(String userId, String operation, Object resourceId, Map<String, Object> details) {
        log(userId, null, operation, resourceId, details);
    }

    /**
     * 记录审计日志（带用户名）
     */
    public void log(String userId, String userName, String operation, Object resourceId, Map<String, Object> details) {
        Map<String, Object> logData = new HashMap<>();
        logData.put("userId", userId);
        logData.put("userName", userName);
        logData.put("operation", operation);
        logData.put("resourceId", resourceId != null ? resourceId.toString() : null);
        if (details != null) {
            logData.put("details", details.toString());
        }
        recordAuditLog(logData);
    }
}
