package rd.service.activity;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.yitter.idgen.YitIdHelper;
import lombok.extern.slf4j.Slf4j;
import rd.dao.mapper.activity.ActivityMapper;
import rd.data.Constants;
import rd.data.code.ServiceCode;
import rd.data.db.table.Activity;
import rd.data.model.request.admin.ActivityInfoDto;
import rd.data.model.response.PaginationWrapper;
import rd.exception.ServiceException;

import rd.service.cache.GlobalCache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class ActivityService {
    private static final Cache<Long, Activity> activityCache = Caffeine.newBuilder()
            .expireAfterAccess(1, TimeUnit.DAYS)
            .initialCapacity(16)
            .maximumSize(64)
            .build();

    private static final Cache<String, Set<Activity>> termCache = Caffeine.newBuilder()
            .expireAfterAccess(1, TimeUnit.DAYS)
            .initialCapacity(16)
            .maximumSize(64)
            .build();

    private final ActivityMapper activityMapper;
    private final RedisTemplate<String, Object> redisTemplate;

    public ActivityService(ActivityMapper activityMapper,
                           RedisTemplate<String, Object> redisTemplate) {
        this.activityMapper = activityMapper;
        this.redisTemplate = redisTemplate;
    }

    public PaginationWrapper<List<Activity>> list(int page, int pageSize) {
        if (pageSize == 0) {
            pageSize = 1024;
        }

        Page<Activity> queryPage = new Page<>(page, pageSize);
        var result = activityMapper
                .selectPage(queryPage, new QueryWrapper<Activity>()
                        .orderByDesc("end_time")
                        .eq("deleted", false)
                );

        return new PaginationWrapper<>(
                (int) result.getCurrent(),
                (int) result.getSize(),
                (int) result.getTotal(),
                result.getRecords()
        );
    }

    public Activity getById(Long actId) {
        return getById(actId, true);
    }

    private Activity getById(Long actId, boolean useCache) {
        var activity = activityCache.getIfPresent(actId);
        if (!useCache || activity == null) {
            activity = activityMapper.selectOne(new QueryWrapper<Activity>()
                    .eq("id", actId)
                    .eq("deleted", false)
                    .last("limit 1")
            );

            if (activity != null) {
                putCache(activity);
            }
        }

        return activity;
    }

    public Set<Activity> getByTerm(String term) {
        var list = termCache.getIfPresent(term);
        if (list == null) {
            var result = activityMapper.selectList(new QueryWrapper<Activity>()
                    .orderByDesc("end_time")
                    .eq("term", term)
                    .eq("deleted", false)
            );
            list = new HashSet<>(result);
            termCache.put(term, list);
        }

        return list;
    }

    private void putCache(Activity activity) {
        activityCache.put(activity.getId(), activity);
        var list = termCache.getIfPresent(activity.getTerm());
        if (list == null) {
            list = new HashSet<>(4);
        }

        list.add(activity);
        termCache.put(activity.getTerm(), list);
    }

    private static final String CurrentActivityRedisKey = "rd:cache:activity:current";

    public Activity getCurrentActivity() {
        var now = LocalDateTime.now();
        var currentActivity = (Activity) redisTemplate.opsForValue().get(CurrentActivityRedisKey);
        if (currentActivity == null) {
            currentActivity = this.getCurrentActivityFromDB(now);
            if (currentActivity == null) {
                return null;
            }

            var expire = Duration.between(now, currentActivity.getEndTime());

            redisTemplate.opsForValue().set(CurrentActivityRedisKey, this.getCurrentActivityFromDB(now), expire);
        }

        return currentActivity;
    }

    public Activity getCurrentActivityFromDB(LocalDateTime now) {
        return activityMapper.selectOne(new QueryWrapper<Activity>()
                .le("start_time", now)
                .ge("end_time", now)
                .orderByDesc("end_time")
                .last("limit 1")
                .eq("deleted", false)
        );
    }

    private List<Activity> checkConflict(ActivityInfoDto dto) {
        // 检查开始时间和结束时间
        if (!dto.start().isBefore(dto.end())) {
            throw ServiceException.error(
                    ServiceCode.DataFormatWrong,
                    String.format("时间参数不正确：开始时间：%s，结束时间：%s",
                            dto.start().format(Constants.DateTime.DEFAULT_DATE_FORMATTER),
                            dto.end().format(Constants.DateTime.DEFAULT_DATE_FORMATTER)
                    )
            );
        }

        // 检查冲突
        return activityMapper.getConflictActivity(dto.start(), dto.end());
    }

    private String makeConflictMsg(List<Activity> conflictActivityList) {
        var stringJoiner = new StringJoiner("，", "一个时间段内只能有一个进行中的活动，冲突的活动：", "");
        conflictActivityList.forEach(act -> stringJoiner.add(
                String.format("%s(%s至%s)",
                        act.getName(),
                        act.getStartTime().format(Constants.DateTime.DEFAULT_DATE_FORMATTER),
                        act.getEndTime().format(Constants.DateTime.DEFAULT_DATE_FORMATTER)
                )
        ));

        return stringJoiner.toString();
    }

    public void addActivity(ActivityInfoDto request) {
        var conflictActivityList = checkConflict(request);
        if (!conflictActivityList.isEmpty()) {
            throw ServiceException.error(ServiceCode.DataConflict, this.makeConflictMsg(conflictActivityList));
        }

        Long id = YitIdHelper.nextId();

        Activity activity = new Activity();
        activity.setId(id);
        activity.setName(request.name());
        activity.setTerm(request.term());
        activity.setStartTime(request.start());
        activity.setEndTime(request.end());

        activityMapper.insert(activity);

        putCache(activity);
        try {
            var now = LocalDateTime.now();
            if (!activity.getStartTime().isAfter(now) && !activity.getEndTime().isBefore(now)) {
                var expire = Duration.between(now, activity.getEndTime());
                redisTemplate.opsForValue().set(CurrentActivityRedisKey, activity, expire);
            }
        } catch (Exception e) {
            log.warn("添加活动处理当前活动redis缓存时出现异常：", e);
        }
    }

    @Transactional
    public void delete(Long actId) {
        Activity activity = getById(actId);
        if (activity == null) {
            return;
        }

        activity.setDeleted(true);
        activity.setEditTime(LocalDateTime.now());

        activityMapper.update(activity, new QueryWrapper<Activity>()
                .eq("id", actId)
                .eq("deleted", false)
        );

        activityCache.invalidate(actId);
        termCache.invalidate(activity.getTerm());
        GlobalCache.ActivityTargetCourseCache.invalidateAll();
        try {
            var currentActivity = (Activity) redisTemplate.opsForValue().get(CurrentActivityRedisKey);
            if (currentActivity != null && currentActivity.getId().equals(actId)) {
                redisTemplate.opsForValue().getAndDelete(CurrentActivityRedisKey);
            }
        } catch (Exception e){
            log.warn("删除活动处理当前活动redis缓存时出现异常：", e);
        }
    }

    @Transactional
    public Activity update(Long actId, ActivityInfoDto request) {
        Activity activity = getById(actId);
        if (activity == null) {
            throw ServiceException.error(ServiceCode.DataNotExists, "活动id不存在");
        }

        // 时间冲突检测
        var conflictActivityList = checkConflict(request);
        if (!conflictActivityList.isEmpty()) {
            throw ServiceException.error(ServiceCode.DataConflict, this.makeConflictMsg(conflictActivityList));
        }

        activity.setName(request.name());
        activity.setTerm(request.term());
        activity.setStartTime(request.start());
        activity.setEndTime(request.end());

        activityMapper.update(activity, new QueryWrapper<Activity>()
                .eq("id", actId)
                .eq("deleted", false)
        );

        var newAct = this.getById(actId, false);
        try {
            var currentActivity = (Activity) redisTemplate.opsForValue().get(CurrentActivityRedisKey);
            if (currentActivity != null && currentActivity.getId().equals(newAct.getId())) {
                var expire = Duration.between(LocalDateTime.now(), newAct.getEndTime());
                redisTemplate.opsForValue().set(CurrentActivityRedisKey, newAct, expire);
            }
        } catch (Exception e){
            log.warn("更新活动处理当前活动redis缓存时出现异常：", e);
        }

        return newAct;
    }
}
