package com.erp.systemtool.service;

import com.erp.systemtool.entity.OperationLog;
import com.erp.systemtool.repository.OperationLogRepository;
import jakarta.persistence.criteria.Predicate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

/**
 * Operation Log Service
 * 
 * Manages operation logs with filtering, statistics, and cleanup.
 * Migrations from PHP OperationLogController.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class OperationLogService {

    private final OperationLogRepository logRepository;

    /**
     * Query operation logs with filters
     */
    public Map<String, Object> queryLogs(Integer tenantId, Map<String, Object> params) {
        int page = params.get("page") != null ? (int) params.get("page") : 1;
        int limit = params.get("limit") != null ? (int) params.get("limit") : 20;

        Pageable pageable = PageRequest.of(page - 1, limit, Sort.by(Sort.Direction.DESC, "createdAt"));

        Specification<OperationLog> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.equal(root.get("tenantId"), tenantId));

            if (params.containsKey("userId") && params.get("userId") != null) {
                predicates.add(cb.equal(root.get("userId"), params.get("userId")));
            }
            if (params.containsKey("userName") && params.get("userName") != null) {
                predicates.add(cb.like(root.get("userName"), "%" + params.get("userName") + "%"));
            }
            if (params.containsKey("module") && params.get("module") != null) {
                predicates.add(cb.equal(root.get("module"), params.get("module")));
            }
            if (params.containsKey("action") && params.get("action") != null) {
                predicates.add(cb.equal(root.get("action"), params.get("action")));
            }
            if (params.containsKey("method") && params.get("method") != null) {
                predicates.add(cb.equal(root.get("method"), params.get("method")));
            }
            if (params.containsKey("path") && params.get("path") != null) {
                predicates.add(cb.like(root.get("path"), "%" + params.get("path") + "%"));
            }
            if (params.containsKey("ip") && params.get("ip") != null) {
                predicates.add(cb.like(root.get("ip"), "%" + params.get("ip") + "%"));
            }
            if (params.containsKey("responseCode") && params.get("responseCode") != null) {
                String rc = (String) params.get("responseCode");
                if ("success".equals(rc)) {
                    predicates.add(cb.lessThan(root.get("responseCode"), 400));
                } else if ("error".equals(rc)) {
                    predicates.add(cb.greaterThanOrEqualTo(root.get("responseCode"), 400));
                } else {
                    predicates.add(cb.equal(root.get("responseCode"), Integer.parseInt(rc)));
                }
            }
            if (params.containsKey("keyword") && params.get("keyword") != null) {
                String keyword = (String) params.get("keyword");
                predicates.add(cb.or(
                    cb.like(root.get("userName"), "%" + keyword + "%"),
                    cb.like(root.get("module"), "%" + keyword + "%"),
                    cb.like(root.get("action"), "%" + keyword + "%"),
                    cb.like(root.get("path"), "%" + keyword + "%"),
                    cb.like(root.get("ip"), "%" + keyword + "%"),
                    cb.like(root.get("remark"), "%" + keyword + "%")
                ));
            }
            if (params.containsKey("dateRange") && params.get("dateRange") != null) {
                String[] dates = ((String) params.get("dateRange")).split(",");
                if (dates.length == 2) {
                    predicates.add(cb.between(root.get("createdAt"),
                        LocalDateTime.parse(dates[0]), 
                        LocalDateTime.parse(dates[1])));
                }
            }
            return cb.and(predicates.toArray(new Predicate[0]));
        };

        Page<OperationLog> pageResult = logRepository.findAll(spec, pageable);

        Map<String, Object> result = new HashMap<>();
        result.put("list", pageResult.getContent());
        result.put("total", pageResult.getTotalElements());
        result.put("current_page", pageResult.getNumber() + 1);
        result.put("last_page", pageResult.getTotalPages());
        return result;
    }

    /**
     * Get log detail by ID
     */
    public Optional<OperationLog> getLogById(Long id, Integer tenantId) {
        return logRepository.findById(id)
            .filter(log -> log.getTenantId().equals(tenantId));
    }

    /**
     * Delete a single log
     */
    @Transactional
    public boolean deleteLog(Long id, Integer tenantId) {
        return logRepository.findById(id)
            .filter(log -> log.getTenantId().equals(tenantId))
            .map(log -> {
                logRepository.delete(log);
                return true;
            })
            .orElse(false);
    }

    /**
     * Batch delete logs
     */
    @Transactional
    public Map<String, Object> batchDelete(List<Long> ids, Integer tenantId) {
        int successCount = 0;
        List<Map<String, Object>> failedLogs = new ArrayList<>();

        for (Long id : ids) {
            if (logRepository.findById(id)
                    .filter(log -> log.getTenantId().equals(tenantId))
                    .isPresent()) {
                logRepository.deleteById(id);
                successCount++;
            } else {
                Map<String, Object> failed = new HashMap<>();
                failed.put("id", id);
                failed.put("reason", "日志不存在或无权访问");
                failedLogs.add(failed);
            }
        }

        Map<String, Object> result = new HashMap<>();
        result.put("success_count", successCount);
        result.put("failed_logs", failedLogs);
        return result;
    }

    /**
     * Clear logs older than specified days
     */
    @Transactional
    public long clearOldLogs(Integer tenantId, Integer days) {
        LocalDateTime cutoffDate = LocalDateTime.now().minusDays(days != null ? days : 30);
        return logRepository.deleteOlderThan(cutoffDate);
    }

    /**
     * Get operation statistics
     */
    public Map<String, Object> getStatistics(Integer tenantId, String startDate, String endDate, String groupBy) {
        LocalDateTime start = startDate != null ? 
            LocalDate.parse(startDate).atStartOfDay() : 
            LocalDateTime.now().minusDays(30);
        LocalDateTime end = endDate != null ? 
            LocalDate.parse(endDate).atTime(LocalTime.MAX) : 
            LocalDateTime.now();

        // Overall stats
        Map<String, Object> overall = new HashMap<>();
        overall.put("total", logRepository.countByModule(tenantId, start, end));

        // Time trend
        List<Object[]> trendData;
        if ("week".equals(groupBy)) {
            // Group by yearweek - simplified
            trendData = logRepository.countByDay(tenantId, start, end);
        } else if ("month".equals(groupBy)) {
            // Group by month - simplified
            trendData = logRepository.countByDay(tenantId, start, end);
        } else {
            trendData = logRepository.countByDay(tenantId, start, end);
        }
        
        List<Map<String, Object>> trend = new ArrayList<>();
        for (Object[] row : trendData) {
            Map<String, Object> item = new HashMap<>();
            item.put("date", row[0]);
            item.put("count", ((Number) row[1]).longValue());
            trend.add(item);
        }

        // Module stats
        List<Object[]> moduleData = logRepository.moduleStats(tenantId, start, end);
        List<Map<String, Object>> modules = new ArrayList<>();
        for (Object[] row : moduleData) {
            Map<String, Object> item = new HashMap<>();
            item.put("module", row[0]);
            item.put("total", ((Number) row[1]).longValue());
            item.put("success", ((Number) row[2]).longValue());
            item.put("error", ((Number) row[3]).longValue());
            modules.add(item);
        }

        // User stats (top 20)
        List<Object[]> userData = logRepository.countByUser(tenantId, start, end);
        List<Map<String, Object>> users = new ArrayList<>();
        for (Object[] row : userData) {
            Map<String, Object> item = new HashMap<>();
            item.put("user_id", row[0]);
            item.put("user_name", row[1]);
            item.put("total", ((Number) row[2]).longValue());
            users.add(item);
        }

        // Response time stats
        Object[] rtStats = logRepository.responseTimeStats(tenantId, start, end);
        Map<String, Object> responseTime = new HashMap<>();
        if (rtStats != null && rtStats.length >= 3) {
            responseTime.put("avg_time", rtStats[0] != null ? ((Number) rtStats[0]).doubleValue() : 0);
            responseTime.put("min_time", rtStats[1] != null ? ((Number) rtStats[1]).intValue() : 0);
            responseTime.put("max_time", rtStats[2] != null ? ((Number) rtStats[2]).intValue() : 0);
        }

        Map<String, Object> result = new HashMap<>();
        result.put("overall", overall);
        result.put("trend", trend);
        result.put("modules", modules);
        result.put("users", users);
        result.put("response_time", responseTime);
        result.put("date_range", Map.of("start_date", start.toLocalDate(), "end_date", end.toLocalDate()));
        return result;
    }

    /**
     * Get distinct modules
     */
    public List<String> getModules(Integer tenantId) {
        return logRepository.findDistinctModules(tenantId);
    }

    /**
     * Get distinct actions
     */
    public List<String> getActions(Integer tenantId) {
        return logRepository.findDistinctActions(tenantId);
    }

    /**
     * Create operation log
     */
    @Transactional
    public OperationLog createLog(OperationLog operationLog) {
        return logRepository.save(operationLog);
    }
}
