package com.example.volunteer.service;

import com.example.volunteer.dto.SignupSearch;
import com.example.volunteer.model.Signup;
import com.example.volunteer.model.Volunteer;
import com.example.volunteer.repository.SignupRepository;
import jakarta.persistence.criteria.Predicate;
import org.springframework.data.domain.PageRequest;
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.util.ArrayList;
import java.util.List;

/**
 * ============================================================================
 * 查询服务 — 样卷 Q4(b) 聚合 + Q4(c) 动态条件
 * ============================================================================
 *
 * 【题目 / Question】
 * EN — Q4(b) Write the JPA Queries to find the top ten members who spent more than a given
 *      amount.   Q4(c) Complete the service method that searches records by any combination
 *      of name, status and date. (2 + 3 marks)
 * 中 — Q4(b) 写出“累计消费超过某金额的前十名会员”的 JPA 查询；
 *      Q4(c) 补全“按姓名、状态、日期任意组合查询”的 Service 方法。（2 + 3 分）
 *
 * 【考试取舍 / Exam trade-off】
 * EN — In an exam, a fixed @Query with "(:param is null or …)" is shorter and safer.
 *      Specification is shown here because the sample paper says "any combination", and
 *      because it is the production answer to the same question.
 * 中 — 考试里用 “(:param is null or …)” 的固定 @Query 更短更稳；
 *      这里用 Specification，是因为样卷强调“任意组合”，而且这是工程上的标准答案。
 */
@Service
public class SignupQueryService {

    private final SignupRepository signupRepository;

    public SignupQueryService(SignupRepository signupRepository) {
        this.signupRepository = signupRepository;
    }

    // ---------------------------------------------------------------- 样卷 Q4(b) 聚合查询

    /**
     * EN — Top N volunteers by total contributed hours.
     * 中 — 按累计时数取前 N 名志愿者。
     *
     * @param minHours 累计时数下限（题目里的 "more than $100,000"）
     * @param limit    取前几名（题目里的 "top ten"）
     */
    @Transactional(readOnly = true)
    public List<VolunteerHours> topVolunteers(int minHours, int limit) {
        List<Object[]> rows = signupRepository.findTopVolunteers(minHours, PageRequest.of(0, limit));

        List<VolunteerHours> result = new ArrayList<>(rows.size());
        for (Object[] row : rows) {
            Volunteer volunteer = (Volunteer) row[0];
            Number total = (Number) row[1];
            result.add(new VolunteerHours(volunteer.getId(), volunteer.getName(), total.longValue()));
        }
        return result;
    }

    // ---------------------------------------------------------------- 样卷 Q4(c) 动态查询

    /**
     * EN — Apply only the filters that are present; newest sign-up first.
     * 中 — 只对“有值”的条件生效，报名日期倒序。
     */
    @Transactional(readOnly = true)
    public List<SignupView> search(SignupSearch search) {

        Specification<Signup> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();

            // 条件 1：活动（样卷里的 "member" 位置）
            if (search.getEventId() != null) {
                predicates.add(cb.equal(root.get("event").get("id"), search.getEventId()));
            }

            // 条件 2：志愿者姓名，忽略大小写的包含匹配
            if (search.getVolunteerName() != null && !search.getVolunteerName().isBlank()) {
                predicates.add(cb.like(
                        cb.lower(root.get("volunteer").get("name")),
                        "%" + search.getVolunteerName().toLowerCase() + "%"));
            }

            // 条件 3：报名日期区间
            if (search.getFrom() != null) {
                predicates.add(cb.greaterThanOrEqualTo(root.get("signupDate"), search.getFrom()));
            }
            if (search.getTo() != null) {
                predicates.add(cb.lessThanOrEqualTo(root.get("signupDate"), search.getTo()));
            }

            // 空数组 = 不加任何过滤（退化成 1=1）
            return cb.and(predicates.toArray(new Predicate[0]));
        };

        return signupRepository.findAll(spec, Sort.by(Sort.Direction.DESC, "signupDate"))
                                .stream()
                                .map(SignupView::from)
                                .toList();
    }
}
