package com.pharmacopoeia.service;

import com.pharmacopoeia.entity.UserEvent;
import com.pharmacopoeia.repository.UserEventRepository;
import jakarta.persistence.criteria.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class AnalyticsService {

    private static final Logger log = LoggerFactory.getLogger(AnalyticsService.class);
    private static final Instant FAR_FUTURE = Instant.parse("2099-12-31T23:59:59Z");

    private final UserEventRepository repo;

    public AnalyticsService(UserEventRepository repo) {
        this.repo = repo;
    }

    @Async
    public void saveEvent(String userKey, String eventType, Map<String, Object> eventData,
                          String pageUrl, String referrer, String ip, String userAgent) {
        try {
            UserEvent event = UserEvent.builder()
                    .userKey(userKey)
                    .eventType(eventType)
                    .eventData(eventData != null ? eventData : Map.of())
                    .pageUrl(pageUrl)
                    .referrer(referrer)
                    .ip(ip)
                    .userAgent(userAgent)
                    .build();
            repo.save(event);
        } catch (Exception e) {
            log.warn("埋点写入失败: type={}, error={}", eventType, e.getMessage());
        }
    }

    // ==================== 埋点事件列表 ====================

    public Map<String, Object> queryEvents(String eventType, String startDate, String endDate,
                                           int page, int pageSize) {
        Instant startTime = parseDate(startDate) != null ? parseDate(startDate) : Instant.EPOCH;
        Instant endTime = endDate != null && !endDate.isBlank()
                ? LocalDate.parse(endDate).plusDays(1).atStartOfDay(ZoneOffset.UTC).toInstant()
                : FAR_FUTURE;

        Specification<UserEvent> spec = buildTimeSpec(eventType, startTime, endTime);
        var pageable = PageRequest.of(page - 1, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"));
        var result = repo.findAll(spec, pageable);

        List<Map<String, Object>> items = result.getContent().stream()
                .map(e -> {
                    Map<String, Object> m = new LinkedHashMap<>();
                    m.put("id", e.getId());
                    m.put("event_type", e.getEventType());
                    m.put("user_key", e.getUserKey());
                    m.put("page_url", e.getPageUrl());
                    m.put("ip", e.getIp());
                    m.put("created_at", e.getCreatedAt());
                    return m;
                })
                .collect(Collectors.toList());

        Map<String, Object> response = new LinkedHashMap<>();
        response.put("items", items);
        response.put("total", result.getTotalElements());
        response.put("page", page);
        response.put("page_size", pageSize);
        return response;
    }

    public Map<String, Object> getEventDetail(long id) {
        var opt = repo.findById(id);
        if (opt.isEmpty()) return Map.of();
        var e = opt.get();
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("id", e.getId());
        m.put("user_key", e.getUserKey());
        m.put("event_type", e.getEventType());
        m.put("event_data", e.getEventData());
        m.put("page_url", e.getPageUrl());
        m.put("referrer", e.getReferrer());
        m.put("ip", e.getIp());
        m.put("user_agent", e.getUserAgent());
        m.put("created_at", e.getCreatedAt());
        return m;
    }

    // ==================== 概览数字 ====================

    public Map<String, Object> queryOverview(String range) {
        String today = LocalDate.now().toString();
        Instant todayStart = parseDate(today);
        Instant todayEnd = LocalDate.parse(today).plusDays(1).atStartOfDay(ZoneOffset.UTC).toInstant();

        Instant startTime;
        Instant endTime;
        String label;

        if ("today".equals(range)) {
            startTime = todayStart;
            endTime = todayEnd;
            label = "today";
        } else if ("week".equals(range)) {
            startTime = todayStart.minus(6, java.time.temporal.ChronoUnit.DAYS);
            endTime = todayEnd;
            label = "week";
        } else {
            startTime = Instant.EPOCH;
            endTime = FAR_FUTURE;
            label = "total";
        }

        Map<String, Object> overview = new LinkedHashMap<>();
        overview.put("range", label);
        overview.put("total_events", repo.countTotal(startTime, endTime));
        overview.put("active_users", repo.countDistinctUsers(startTime, endTime));
        overview.put("page_views", repo.countByType("page_view", startTime, endTime));
        overview.put("searches", repo.countByType("search", startTime, endTime));
        overview.put("ai_qa", repo.countByType("chat", startTime, endTime));
        return overview;
    }

    // ==================== 维度分页统计 ====================

    /** 分页维度统计：ip / url / type / user / user_ip */
    public Map<String, Object> queryDimension(String dimension, String startDate, String endDate,
                                              int page, int pageSize) {
        Instant startTime = parseDate(startDate) != null ? parseDate(startDate) : Instant.EPOCH;
        Instant endTime = endDate != null && !endDate.isBlank()
                ? LocalDate.parse(endDate).plusDays(1).atStartOfDay(ZoneOffset.UTC).toInstant()
                : FAR_FUTURE;

        Page<Object[]> result;
        switch (dimension != null ? dimension : "") {
            case "ip":
                result = repo.countByIpPaged(startTime, endTime,
                        PageRequest.of(page - 1, pageSize));
                break;
            case "url":
                result = repo.countByPageUrlPaged(startTime, endTime,
                        PageRequest.of(page - 1, pageSize));
                break;
            case "type":
                result = repo.countByEventTypePaged(startTime, endTime,
                        PageRequest.of(page - 1, pageSize));
                break;
            case "user":
                result = repo.countByUserKeyPaged(startTime, endTime,
                        PageRequest.of(page - 1, pageSize));
                break;
            case "user_ip":
                result = repo.countByUserIpPaged(startTime, endTime,
                        PageRequest.of(page - 1, pageSize));
                break;
            default:
                return Map.of();
        }

        List<Map<String, Object>> items = mapResult(result.getContent(), dimension);
        Map<String, Object> response = new LinkedHashMap<>();
        response.put("dimension", dimension);
        response.put("items", items);
        response.put("total", result.getTotalElements());
        response.put("page", page);
        response.put("page_size", pageSize);
        return response;
    }

    private List<Map<String, Object>> mapResult(List<Object[]> rows, String dimension) {
        return rows.stream()
                .map(row -> {
                    Map<String, Object> m = new LinkedHashMap<>();
                    if ("user_ip".equals(dimension)) {
                        m.put("user_key", row[0] != null ? row[0] : "");
                        m.put("ip", row[1] != null ? row[1] : "");
                        m.put("count", (Long) row[2]);
                    } else {
                        m.put("key", row[0] != null ? row[0] : "");
                        m.put("count", (Long) row[1]);
                    }
                    return m;
                })
                .collect(Collectors.toList());
    }

    // ==================== Helpers ====================

    private Specification<UserEvent> buildTimeSpec(String eventType, Instant startTime, Instant endTime) {
        return (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (eventType != null && !eventType.isBlank()) {
                predicates.add(cb.equal(root.get("eventType"), eventType));
            }
            predicates.add(cb.greaterThanOrEqualTo(root.get("createdAt"), startTime));
            predicates.add(cb.lessThan(root.get("createdAt"), endTime));
            return cb.and(predicates.toArray(new Predicate[0]));
        };
    }

    private Instant parseDate(String date) {
        if (date == null || date.isBlank()) return null;
        return LocalDate.parse(date).atStartOfDay(ZoneOffset.UTC).toInstant();
    }
}
