package com.erp.systemtool.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Monitoring Service
 * 
 * Monitors API requests, queue backlog, and system health.
 * Migrations from PHP MonitoringService.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MonitoringService {

    private static final String API_REQUEST_KEY_PREFIX = "monitoring:api_requests:";
    private static final String QUEUE_KEY_PREFIX = "queues:";
    private static final int RETENTION_SECONDS = 3600; // 1 hour

    private final StringRedisTemplate redisTemplate;
    private final NotificationService notificationService;

    // Configurable queue names
    private static final List<String> MONITORED_QUEUES = List.of(
        "erp:pull-order",
        "erp:waybill",
        "erp:logistics",
        "erp:cloud-warehouse",
        "erp:notification"
    );

    /**
     * Record an API request
     */
    public void recordApiRequest(String endpoint, long durationMs, int statusCode) {
        String key = API_REQUEST_KEY_PREFIX + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-H"));
        long now = Instant.now().getEpochSecond();

        String json = String.format(
            "{\"endpoint\":\"%s\",\"duration\":%d,\"status\":%d,\"time\":%d}",
            endpoint, durationMs, statusCode, now
        );

        redisTemplate.opsForZSet().add(key, json, now);

        // Cleanup old entries
        redisTemplate.opsForZSet().removeRangeByScore(key, 0, now - RETENTION_SECONDS);
    }

    /**
     * Get API statistics for an endpoint
     */
    public Map<String, Object> getApiStats(String endpoint, int minutes) {
        String key = API_REQUEST_KEY_PREFIX + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-H"));
        long startTime = Instant.now().minusSeconds(minutes * 60L).getEpochSecond();
        long now = Instant.now().getEpochSecond();

        Set<String> requests = redisTemplate.opsForZSet().rangeByScore(key, startTime, now);

        if (requests == null || requests.isEmpty()) {
            return Map.of(
                "count", 0,
                "avg_duration", 0.0,
                "error_rate", 0.0,
                "p95_duration", 0,
                "p99_duration", 0
            );
        }

        // Filter by endpoint if not "all"
        List<Map<String, Object>> parsedRequests = requests.stream()
            .map(this::parseRequestJson)
            .filter(m -> "all".equals(endpoint) || endpoint.equals(m.get("endpoint")))
            .collect(Collectors.toList());

        if (parsedRequests.isEmpty()) {
            return Map.of(
                "count", 0,
                "avg_duration", 0.0,
                "error_rate", 0.0,
                "p95_duration", 0,
                "p99_duration", 0
            );
        }

        int total = parsedRequests.size();
        int errors = 0;
        List<Long> durations = new ArrayList<>();

        for (Map<String, Object> req : parsedRequests) {
            int status = (int) req.get("status");
            if (status >= 400) {
                errors++;
            }
            durations.add((Long) req.get("duration"));
        }

        Collections.sort(durations);

        int p95Index = (int) Math.ceil(total * 0.95) - 1;
        int p99Index = (int) Math.ceil(total * 0.99) - 1;

        double avgDuration = durations.stream().mapToLong(Long::longValue).average().orElse(0);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("count", total);
        result.put("avg_duration", avgDuration);
        result.put("error_rate", (double) errors / total);
        result.put("p95_duration", durations.get(Math.min(p95Index, durations.size() - 1)));
        result.put("p99_duration", durations.get(Math.min(p99Index, durations.size() - 1)));
        return result;
    }

    private Map<String, Object> parseRequestJson(String json) {
        Map<String, Object> result = new HashMap<>();
        try {
            // Simple JSON parsing without external library
            result.put("endpoint", extractJsonValue(json, "endpoint"));
            result.put("duration", Long.parseLong(extractJsonValue(json, "duration")));
            result.put("status", Integer.parseInt(extractJsonValue(json, "status")));
            result.put("time", Long.parseLong(extractJsonValue(json, "time")));
        } catch (Exception e) {
            log.warn("Failed to parse request JSON: {}", json);
        }
        return result;
    }

    private String extractJsonValue(String json, String key) {
        String pattern = "\"" + key + "\":";
        int start = json.indexOf(pattern) + pattern.length();
        int end = json.indexOf(",", start);
        if (end == -1) {
            end = json.indexOf("}", start);
        }
        String value = json.substring(start, end).trim();
        if (value.startsWith("\"")) {
            value = value.substring(1, value.length() - 1);
        }
        return value;
    }

    /**
     * Check queue backlog
     */
    public List<Map<String, Object>> checkQueueBacklog() {
        List<Map<String, Object>> backlogs = new ArrayList<>();

        for (String queue : MONITORED_QUEUES) {
            Long size = redisTemplate.opsForList().size(QUEUE_KEY_PREFIX + queue);
            if (size != null && size > 100) {
                Map<String, Object> backlog = new LinkedHashMap<>();
                backlog.put("queue", queue);
                backlog.put("size", size);
                backlog.put("level", size > 1000 ? "critical" : "warning");
                backlogs.add(backlog);
            }
        }

        return backlogs;
    }

    /**
     * Send alert notification
     */
    public void alert(String level, String title, String message) {
        log.warn("[{}] {}: {}", level, title, message);

        if ("critical".equals(level) || "warning".equals(level)) {
            String markdown = String.format(
                "### [%s] %s\n\n%s\n\n> Time: %s",
                level.toUpperCase(), title, message,
                LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
            );
            notificationService.sendDingTalkNotification(markdown);
        }
    }

    /**
     * Scheduled health check (runs every 5 minutes)
     */
    @Scheduled(fixedRate = 300000) // 5 minutes
    public void checkAndAlert() {
        // Check API error rate
        Map<String, Object> apiStats = getApiStats("all", 5);
        double errorRate = (double) apiStats.getOrDefault("error_rate", 0.0);
        
        if (errorRate > 0.01) {
            alert("warning", "High API Error Rate", String.format(
                "API error rate: %.2f%%, requests: %d",
                errorRate * 100, apiStats.get("count")
            ));
        }

        // Check P99 latency
        long p99Duration = (long) apiStats.getOrDefault("p99_duration", 0L);
        if (p99Duration > 2000) {
            alert("warning", "High API Latency", String.format(
                "P99 latency: %dms, requests: %d",
                p99Duration, apiStats.get("count")
            ));
        }

        // Check queue backlog
        List<Map<String, Object>> backlogs = checkQueueBacklog();
        for (Map<String, Object> backlog : backlogs) {
            alert(
                (String) backlog.get("level"),
                "Queue Backlog",
                String.format("%s queue backlog: %d items",
                    backlog.get("queue"), backlog.get("size"))
            );
        }
    }

    /**
     * Get overall system health status
     */
    public Map<String, Object> getHealthStatus() {
        Map<String, Object> status = new LinkedHashMap<>();
        
        // API stats (last 5 minutes)
        status.put("api", getApiStats("all", 5));
        
        // Queue backlog
        status.put("queues", checkQueueBacklog());
        
        // Redis connectivity
        try {
            redisTemplate.getConnectionFactory().getConnection().ping();
            status.put("redis", "connected");
        } catch (Exception e) {
            status.put("redis", "disconnected");
            status.put("redis_error", e.getMessage());
        }
        
        status.put("timestamp", LocalDateTime.now().toString());
        return status;
    }
}
