package com.jannchie.biliob.service.impl;

import com.jannchie.biliob.constant.TaskStatusEnum;
import com.jannchie.biliob.repository.TracerRepository;
import com.jannchie.biliob.service.TracerService;
import com.jannchie.biliob.utils.RedisOps;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.util.*;

import static com.jannchie.biliob.constant.TaskTypeEnum.GET_ALL;
import static com.jannchie.biliob.constant.TaskTypeEnum.GET_RUNNING;

/**
 * @author jannchie
 */
@Service
public class TracerServiceImpl implements TracerService {

    private static final Integer MAX_ONLINE_PLAY_RANGE = 30;
    private static final Integer HOUR_IN_DAY = 24;

    private static final Logger logger = LogManager.getLogger(VideoServiceImpl.class);
    private final MongoTemplate mongoTemplate;
    private final TracerRepository tracerRepository;
    private final RedisOps redisOps;

    @Autowired
    public TracerServiceImpl(
            MongoTemplate mongoTemplate, TracerRepository tracerRepository, RedisOps redisOps) {
        this.mongoTemplate = mongoTemplate;
        this.tracerRepository = tracerRepository;
        this.redisOps = redisOps;
    }

    /**
     * It is the function to get authors' queue status.
     *
     * @return The authors' queue status.
     */
    @Override
    public ResponseEntity getAuthorQueueStatus() {
        Map<String, Long> result = new HashMap<>(1);
        Long authorCrawlTasksQueueLength = redisOps.getAuthorQueueLength();
        result.put("length", authorCrawlTasksQueueLength);
        return new ResponseEntity<>(result, HttpStatus.OK);
    }

    /**
     * It is the function to get videos' queue status.
     *
     * @return The videos' queue status.
     */
    @Override
    public ResponseEntity getVideoQueueStatus() {
        Map<String, Long> result = new HashMap<>(1);
        Long videoCrawlTasksQueueLength = redisOps.getVideoQueueLength();
        result.put("length", videoCrawlTasksQueueLength);
        return new ResponseEntity<>(result, HttpStatus.OK);
    }

    /**
     * Get the slice of exists task of the system.
     *
     * <p>It is able to get the status of Biliob scheduler and Biliob spider.
     *
     * @param page     The page number of the task slice.
     * @param pagesize The page size of the task slice.
     * @return tTe slice of exists task of the system.
     */
    @Override
    public ResponseEntity sliceExistsTask(Integer page, Integer pagesize) {
        return new ResponseEntity<>(
                tracerRepository.findTracerByClassNameOrderByUpdateTimeDesc(
                        "ExistsTask", PageRequest.of(page, pagesize)),
                HttpStatus.OK);
    }

    /**
     * Get the slice of progress task of the system.
     *
     * <p>It is able to get the status of Biliob link generate task.
     *
     * @param page     The page number of the task slice.
     * @param pagesize The page size of the task slice.
     * @return tTe slice of exists task of the system.
     */
    @Override
    public ResponseEntity sliceProgressTask(Integer page, Integer pagesize) {
        return new ResponseEntity<>(
                tracerRepository.findTracerByClassNameOrderByUpdateTimeDesc(
                        "ProgressTask", PageRequest.of(page, pagesize)),
                HttpStatus.OK);
    }

    @Override
    public ResponseEntity sliceSpiderTask(Integer page, Integer pagesize, Integer type) {
        if (type.equals(GET_ALL.value)) {
            return new ResponseEntity<>(
                    tracerRepository.findTracerByClassName("SpiderTask", PageRequest.of(page, pagesize)),
                    HttpStatus.OK);
        } else if (type.equals(GET_RUNNING.value)) {
            return new ResponseEntity<>(
                    tracerRepository.findTracerByClassNameAndStatus(
                            "SpiderTask", TaskStatusEnum.ALIVE.value, PageRequest.of(page, pagesize)),
                    HttpStatus.OK);
        }
        return new ResponseEntity<>(
                tracerRepository.findTracerByClassName("SpiderTask", PageRequest.of(page, pagesize)),
                HttpStatus.OK);
    }

    /**
     * Get the data for the dashboard page.
     *
     * @return tTe slice of exists task of the system.
     */
    @Override
    public ResponseEntity getDashboardData() {
        Map<String, Object> resultMap = new HashMap<>(10);

        getCrawlCountAggregationData(resultMap);
        getSumSpiderCountData(resultMap);
        getBucketUserCreditList(resultMap);
        getCheckedInCount(resultMap);
        getUserCount(resultMap);
        getLatestProgressTask(resultMap);
        getRecordCount(resultMap);
        getLatestSpiderTask(resultMap);

        return new ResponseEntity<>(resultMap, HttpStatus.OK);
    }

    @Override
    public ResponseEntity getLatestProgressTaskResponse() {
        Map<String, Object> resultMap = new HashMap<>(2);
        getLatestProgressTask(resultMap);
        return new ResponseEntity<>(resultMap, HttpStatus.OK);
    }

    /**
     * Get latest spider task response.
     *
     * @return response entity of latest spider task.
     */
    @Override
    public ResponseEntity getLatestSpiderTaskResponse() {
        Map<String, Object> resultMap = new HashMap<>(2);
        getLatestSpiderTask(resultMap);
        return new ResponseEntity<>(resultMap, HttpStatus.OK);
    }

    @Override
    public ResponseEntity getHistoryQueueStatus() {
        List data = mongoTemplate.find(new Query().with(Sort.by(Sort.Direction.DESC, "date")).limit(100), Map.class, "spider_queue_status");
        Collections.reverse(data);
        return ResponseEntity.ok(data);
    }

    private void getLatestSpiderTask(Map<String, Object> resultMap) {
        resultMap.put("authorCrawlLength", redisOps.getAuthorQueueLength());
        resultMap.put("videoCrawlLength", redisOps.getVideoQueueLength());
    }

    private void getBucketUserCreditList(Map<String, Object> resultMap) {
        Aggregation bucketUserCreditAggregation =
                Aggregation.newAggregation(Aggregation.bucketAuto("exp", 20));

        AggregationResults<Map> bucketUserCreditAggregationResult =
                mongoTemplate.aggregate(bucketUserCreditAggregation, "user", Map.class);

        ArrayList bucketUserCreditList =
                (ArrayList) bucketUserCreditAggregationResult.getRawResults().get("results");

        resultMap.put("userBucketResult", bucketUserCreditList);
    }

    private void getSumSpiderCountData(Map<String, Object> resultMap) {
        Integer sumSpiderCount =
                tracerRepository.countTracerTaskByClassNameAndStatus(
                        "SpiderTask", TaskStatusEnum.ALIVE.value);
        resultMap.put("sumSpiderCount", sumSpiderCount);
    }

    private void getCrawlCountAggregationData(Map<String, Object> resultMap) {
        Aggregation crawlCountAggregation =
                Aggregation.newAggregation(
                        Aggregation.match(Criteria.where("class_name").is("SpiderTask")),
                        Aggregation.group("class_name").sum("crawl_count").as("sumCrawlCount"));

        AggregationResults<Map> crawlCountAggregationResult =
                mongoTemplate.aggregate(crawlCountAggregation, "tracer", Map.class);
        resultMap.putAll(Objects.requireNonNull(crawlCountAggregationResult.getUniqueMappedResult()));
    }

    private void getCheckedInCount(Map<String, Object> resultMap) {
        Long count = mongoTemplate.count(new Query(), "check_in");
        resultMap.put("checkedInCount", count);
    }

    private void getUserCount(Map<String, Object> resultMap) {
        Long count = mongoTemplate.count(new Query(), "user");
        resultMap.put("userCount", count);
    }

    private void getLatestProgressTask(Map<String, Object> resultMap) {
        Map lastRunningProgressTask =
                mongoTemplate.findOne(
                        Query.query(
                                Criteria.where("class_name")
                                        .is("ProgressTask")
                                        .and("status")
                                        .is(TaskStatusEnum.UPDATE.value))
                                .with(Sort.by("start_time").descending()),
                        Map.class,
                        "tracer");
        Map lastFinishedProgressTask =
                mongoTemplate.findOne(
                        Query.query(
                                Criteria.where("class_name")
                                        .is("ProgressTask")
                                        .and("status")
                                        .is(TaskStatusEnum.FINISHED.value))
                                .with(Sort.by("start_time").descending()),
                        Map.class,
                        "tracer");
        resultMap.put("lastRunningProgressTask", lastRunningProgressTask);
        resultMap.put("lastFinishedProgressTask", lastFinishedProgressTask);
    }

    private void getWeeklyCheckIn(Map<String, Object> resultMap) {
        resultMap.put(
                "weeklyCheckIn",
                mongoTemplate
                        .aggregate(
                                Aggregation.newAggregation(
                                        Aggregation.match(Criteria.where("message").is("签到")),
                                        Aggregation.project()
                                                .andExpression("week(_id)")
                                                .as("week")
                                                .andExpression("year(_id)")
                                                .as("year"),
                                        Aggregation.group("year", "week").count().as("count"),
                                        Aggregation.sort(Sort.by("year").ascending().and(Sort.by("week").ascending())),
                                        Aggregation.skip(1L),
                                        Aggregation.limit(10)),
                                "user_record",
                                Map.class)
                        .getMappedResults());
    }

    private void getMonthlySignIn(Map<String, Object> resultMap) {
        resultMap.put(
                "monthlySignIn",
                mongoTemplate
                        .aggregate(
                                Aggregation.newAggregation(
                                        Aggregation.project()
                                                .andExpression("month(_id)")
                                                .as("month")
                                                .andExpression("year(_id)")
                                                .as("year"),
                                        Aggregation.group("year", "month").count().as("count"),
                                        Aggregation.sort(Sort.by("year").ascending().and(Sort.by("month").ascending())),
                                        Aggregation.skip(1L),
                                        Aggregation.limit(10)),
                                "user",
                                Map.class)
                        .getMappedResults());
    }

    private void getRecordCount(Map<String, Object> resultMap) {
        resultMap.put("recordCount", mongoTemplate.count(new Query(), "user_record"));
    }

    @Override
    public ResponseEntity listAuthorVisitRecord(Integer limit) {
        List data = mongoTemplate.aggregate(Aggregation.newAggregation(
                Aggregation.project().and("date").dateAsFormattedString("%Y-%m-%d").as("date"),
                Aggregation.group("date").count().as("count"),
                Aggregation.sort(Sort.Direction.DESC, "_id"),
                Aggregation.limit(limit),
                Aggregation.sort(Sort.Direction.ASC, "_id")
        ), "author_visit", Map.class).getMappedResults();
        return ResponseEntity.ok(data);
    }

    @Override
    public ResponseEntity listVideoVisitRecord(Integer limit) {
        List data = mongoTemplate.aggregate(Aggregation.newAggregation(
                Aggregation.project().and("date").dateAsFormattedString("%Y-%m-%d").as("date"),
                Aggregation.group("date").count().as("count"),
                Aggregation.sort(Sort.Direction.DESC, "_id"),
                Aggregation.limit(limit),
                Aggregation.sort(Sort.Direction.ASC, "_id")
        ), "video_visit", Map.class).getMappedResults();
        return ResponseEntity.ok(data);
    }
}