package com.erp.approval.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.erp.approval.dto.OperationLogQueryRequest;
import com.erp.approval.entity.OperationLog;
import com.erp.approval.repository.OperationLogRepository;
import com.erp.approval.service.OperationLogService;
import com.erp.common.core.model.PageResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.OutputStreamWriter;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;

/**
 * 操作日志服务实现
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class OperationLogServiceImpl implements OperationLogService {

    private final OperationLogRepository operationLogRepository;
    private final ObjectMapper objectMapper;

    private static final int MAX_EXPORT_COUNT = 10000;
    private static final String[] CSV_HEADERS = {
            "ID", "操作人", "操作人ID", "模块", "操作", "请求方法",
            "请求路径", "IP地址", "响应状态码", "执行时长(ms)", "操作时间", "备注"
    };

    @Override
    public PageResponse<OperationLog> listLogs(Long tenantId, OperationLogQueryRequest request) {
        LambdaQueryWrapper<OperationLog> queryWrapper = buildQueryWrapper(tenantId, request);

        int page = request.getPage() != null ? request.getPage() : 1;
        int pageSize = request.getPageSize() != null ? request.getPageSize() : 20;
        Page<OperationLog> pageResult = operationLogRepository.selectPage(
                new Page<>(page, pageSize),
                queryWrapper
        );

        return PageResponse.of(pageResult.getRecords(), pageResult.getTotal(), page, pageSize);
    }

    @Override
    public OperationLog getLogById(Long tenantId, Long id) {
        LambdaQueryWrapper<OperationLog> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(OperationLog::getId, id)
                    .eq(OperationLog::getTenantId, tenantId);
        return operationLogRepository.selectOne(queryWrapper);
    }

    @Override
    public Map<String, Object> getStatistics(Long tenantId, String startDate, String endDate) {
        if (!StringUtils.hasText(startDate)) {
            startDate = LocalDateTime.now().minusDays(7).toLocalDate().toString();
        }
        if (!StringUtils.hasText(endDate)) {
            endDate = LocalDateTime.now().toLocalDate().toString();
        }

        String startDateTime = startDate + " 00:00:00";
        String endDateTime = endDate + " 23:59:59";

        Map<String, Object> result = new HashMap<>();

        // 统计成功/失败数量
        Map<String, Object> countResult = operationLogRepository.countSuccessAndError(tenantId, startDateTime, endDateTime);
        long successCount = countResult.get("success_count") != null ? ((Number) countResult.get("success_count")).longValue() : 0;
        long errorCount = countResult.get("error_count") != null ? ((Number) countResult.get("error_count")).longValue() : 0;
        long total = successCount + errorCount;

        result.put("total", total);
        result.put("success", successCount);
        result.put("error", errorCount);
        result.put("success_rate", total > 0 ? Math.round(successCount * 10000.0 / total) / 100.0 : 0);

        // 按模块统计
        List<Map<String, Object>> moduleStats = operationLogRepository.statsByModuleAndAction(tenantId, startDateTime, endDateTime);
        Map<String, Long> byModule = new LinkedHashMap<>();
        for (Map<String, Object> item : moduleStats) {
            String module = String.valueOf(item.get("module"));
            long count = ((Number) item.get("count")).longValue();
            byModule.merge(module, count, Long::sum);
        }
        result.put("by_module", byModule);

        // 按用户统计
        List<Map<String, Object>> userStats = operationLogRepository.statsByUser(tenantId, startDateTime, endDateTime);
        Map<String, Long> byUser = new LinkedHashMap<>();
        for (Map<String, Object> item : userStats) {
            String userName = String.valueOf(item.get("user_name"));
            long count = ((Number) item.get("count")).longValue();
            byUser.put(userName, count);
        }
        result.put("by_user", byUser);

        return result;
    }

    @Override
    @Transactional
    public OperationLog log(String module, String action, String method, String path,
                            Map<String, Object> requestParams, Integer responseCode, Integer executionTime, String remark) {
        OperationLog operationLog = new OperationLog();
        operationLog.setTenantId(getCurrentTenantId());
        operationLog.setUserId(getCurrentUserId());
        operationLog.setUserName(getCurrentUserName());
        operationLog.setModule(module);
        operationLog.setAction(action);
        operationLog.setMethod(method);
        operationLog.setPath(path);
        operationLog.setIp(getClientIp());
        operationLog.setUserAgent(getUserAgent());
        operationLog.setRequestParams(toJson(requestParams));
        operationLog.setResponseCode(responseCode);
        operationLog.setExecutionTime(executionTime);
        operationLog.setRemark(remark);
        operationLog.setCreatedAt(LocalDateTime.now());

        operationLogRepository.insert(operationLog);
        return operationLog;
    }

    @Override
    public void exportCsv(Long tenantId, OperationLogQueryRequest request, HttpServletResponse response) throws Exception {
        List<OperationLog> data = queryForExport(tenantId, request);
        String fileName = "operation_logs_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")) + ".csv";

        response.setContentType("text/csv");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));

        try (OutputStreamWriter writer = new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8);
             CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT.builder()
                     .setHeader(CSV_HEADERS)
                     .build())) {

            // BOM for Excel UTF-8 compatibility
            writer.write("\uFEFF");

            for (OperationLog log : data) {
                csvPrinter.printRecord(
                        log.getId(),
                        log.getUserName(),
                        log.getUserId(),
                        log.getModule(),
                        log.getAction(),
                        log.getMethod(),
                        log.getPath(),
                        log.getIp(),
                        log.getResponseCode(),
                        log.getExecutionTime(),
                        log.getCreatedAt() != null ? log.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : "",
                        log.getRemark() != null ? log.getRemark() : ""
                );
            }
        }
        log.info("✅ CSV导出完成，共 {} 条记录", data.size());
    }

    @Override
    public void exportExcel(Long tenantId, OperationLogQueryRequest request, HttpServletResponse response) throws Exception {
        List<OperationLog> data = queryForExport(tenantId, request);
        String fileName = "operation_logs_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")) + ".xlsx";

        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));

        List<OperationLogExcelDTO> excelData = data.stream()
                .map(log -> OperationLogExcelDTO.builder()
                        .id(log.getId())
                        .userName(log.getUserName())
                        .userId(log.getUserId())
                        .module(log.getModule())
                        .action(log.getAction())
                        .method(log.getMethod())
                        .path(log.getPath())
                        .ip(log.getIp())
                        .responseCode(log.getResponseCode())
                        .executionTime(log.getExecutionTime())
                        .createdAt(log.getCreatedAt() != null ? log.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : "")
                        .remark(log.getRemark() != null ? log.getRemark() : "")
                        .build())
                .toList();

        com.alibaba.excel.EasyExcel.write(response.getOutputStream(), OperationLogExcelDTO.class)
                .sheet("操作日志")
                .doWrite(excelData);

        log.info("✅ Excel导出完成，共 {} 条记录", data.size());
    }

    @Override
    @Async
    public CompletableFuture<String> exportExcelAsync(Long tenantId, OperationLogQueryRequest request) throws Exception {
        // 实际实现中，可以将文件上传到OSS或MinIO，返回下载URL
        // 这里简化处理，返回一个任务ID
        String taskId = UUID.randomUUID().toString();
        log.info("异步导出任务已创建: {}", taskId);

        // 异步处理逻辑（可以通过消息队列处理更复杂的场景）
        // 实际项目中建议使用：
        // 1. 消息队列（Redis Queue / RabbitMQ / Kafka）
        // 2. 任务表 + 定时任务
        // 3. 文件存储（OSS / MinIO）

        return CompletableFuture.completedFuture(taskId);
    }

    @Override
    public List<OperationLog> queryForExport(Long tenantId, OperationLogQueryRequest request) {
        LambdaQueryWrapper<OperationLog> queryWrapper = buildQueryWrapper(tenantId, request);
        queryWrapper.last("LIMIT " + MAX_EXPORT_COUNT);
        return operationLogRepository.selectList(queryWrapper);
    }

    // ==================== 私有方法 ====================

    private LambdaQueryWrapper<OperationLog> buildQueryWrapper(Long tenantId, OperationLogQueryRequest request) {
        LambdaQueryWrapper<OperationLog> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(OperationLog::getTenantId, tenantId);

        if (StringUtils.hasText(request.getAction())) {
            queryWrapper.eq(OperationLog::getAction, request.getAction());
        }

        if (StringUtils.hasText(request.getModule())) {
            queryWrapper.eq(OperationLog::getModule, request.getModule());
        }

        if (request.getUserId() != null) {
            queryWrapper.eq(OperationLog::getUserId, request.getUserId());
        }

        if (StringUtils.hasText(request.getUserName())) {
            queryWrapper.like(OperationLog::getUserName, request.getUserName());
        }

        if (StringUtils.hasText(request.getStartDate())) {
            queryWrapper.ge(OperationLog::getCreatedAt, request.getStartDate() + " 00:00:00");
        }

        if (StringUtils.hasText(request.getEndDate())) {
            queryWrapper.le(OperationLog::getCreatedAt, request.getEndDate() + " 23:59:59");
        }

        if (StringUtils.hasText(request.getKeyword())) {
            queryWrapper.and(wrapper -> wrapper
                    .like(OperationLog::getModule, request.getKeyword())
                    .or().like(OperationLog::getAction, request.getKeyword())
                    .or().like(OperationLog::getPath, request.getKeyword())
                    .or().like(OperationLog::getRemark, request.getKeyword())
            );
        }

        // 排序
        String sortField = request.getSortField();
        String sortOrder = request.getSortOrder();
        if ("created_at".equals(sortField)) {
            if ("asc".equalsIgnoreCase(sortOrder)) {
                queryWrapper.orderByAsc(OperationLog::getCreatedAt);
            } else {
                queryWrapper.orderByDesc(OperationLog::getCreatedAt);
            }
        } else if ("execution_time".equals(sortField)) {
            if ("asc".equalsIgnoreCase(sortOrder)) {
                queryWrapper.orderByAsc(OperationLog::getExecutionTime);
            } else {
                queryWrapper.orderByDesc(OperationLog::getExecutionTime);
            }
        } else if ("response_code".equals(sortField)) {
            if ("asc".equalsIgnoreCase(sortOrder)) {
                queryWrapper.orderByAsc(OperationLog::getResponseCode);
            } else {
                queryWrapper.orderByDesc(OperationLog::getResponseCode);
            }
        } else {
            // 默认按ID倒序
            if ("asc".equalsIgnoreCase(sortOrder)) {
                queryWrapper.orderByAsc(OperationLog::getId);
            } else {
                queryWrapper.orderByDesc(OperationLog::getId);
            }
        }

        return queryWrapper;
    }

    private Long getCurrentTenantId() {
        try {
            Class<?> tenantContextClass = Class.forName("com.erp.tenant.config.TenantContext");
            Object tenantId = tenantContextClass.getMethod("getCurrentTenantId").invoke(null);
            if (tenantId != null) {
                return (Long) tenantId;
            }
        } catch (Exception e) {
            log.trace("无法从TenantContext获取租户ID", e);
        }
        return 0L;
    }

    private Long getCurrentUserId() {
        try {
            Class<?> userContextClass = Class.forName("com.erp.common.context.UserContext");
            Object userId = userContextClass.getMethod("getCurrentUserId").invoke(null);
            if (userId != null) {
                return (Long) userId;
            }
        } catch (Exception e) {
            log.trace("无法从UserContext获取用户ID", e);
        }
        return 0L;
    }

    private String getCurrentUserName() {
        try {
            Class<?> userContextClass = Class.forName("com.erp.common.context.UserContext");
            Object username = userContextClass.getMethod("getCurrentUsername").invoke(null);
            if (username != null) {
                return (String) username;
            }
        } catch (Exception e) {
            log.trace("无法从UserContext获取用户名", e);
        }
        return "System";
    }

    private String getClientIp() {
        try {
            HttpServletRequest request = getCurrentRequest();
            if (request != null) {
                String ip = request.getHeader("X-Forwarded-For");
                if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
                    ip = request.getHeader("X-Real-IP");
                }
                if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
                    ip = request.getRemoteAddr();
                }
                return ip;
            }
        } catch (Exception e) {
            log.debug("获取客户端IP失败", e);
        }
        return "0.0.0.0";
    }

    private String getUserAgent() {
        try {
            HttpServletRequest request = getCurrentRequest();
            if (request != null) {
                return request.getHeader("User-Agent");
            }
        } catch (Exception e) {
            log.debug("获取User-Agent失败", e);
        }
        return "unknown";
    }

    private HttpServletRequest getCurrentRequest() {
        try {
            return ((org.springframework.web.context.request.ServletRequestAttributes)
                    org.springframework.web.context.request.RequestContextHolder.getRequestAttributes())
                    .getRequest();
        } catch (Exception e) {
            return null;
        }
    }

    private String toJson(Map<String, Object> map) {
        if (map == null) {
            return null;
        }
        try {
            return objectMapper.writeValueAsString(map);
        } catch (JsonProcessingException e) {
            log.error("序列化Map失败: {}", map, e);
            return null;
        }
    }

    /**
     * Excel导出DTO
     */
    @lombok.Data
    @lombok.Builder
    @lombok.NoArgsConstructor
    @lombok.AllArgsConstructor
    public static class OperationLogExcelDTO {
        private Long id;
        private String userName;
        private Long userId;
        private String module;
        private String action;
        private String method;
        private String path;
        private String ip;
        private Integer responseCode;
        private Integer executionTime;
        private String createdAt;
        private String remark;
    }
}
