package com.test.musichouduan.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.test.musichouduan.dto.PageResult;
import com.test.musichouduan.dto.SingerDTO;
import com.test.musichouduan.dto.MusicDTO;
import com.test.musichouduan.entity.Singer;
import com.test.musichouduan.entity.SingerType;
import com.test.musichouduan.entity.User;
import com.test.musichouduan.entity.Music;
import com.test.musichouduan.exception.BusinessException;
import com.test.musichouduan.repository.MusicRepository;
import com.test.musichouduan.repository.SingerRepository;
import com.test.musichouduan.repository.CommentRepository;
import com.test.musichouduan.repository.SingerTypeRepository;
import com.test.musichouduan.repository.UserRepository;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.test.musichouduan.repository.UserSingerRepository;
import com.test.musichouduan.service.FileService;
import com.test.musichouduan.service.SingerService;
import com.test.musichouduan.util.HotSingerScoreUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.CollectionUtils;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.Comparator;
import org.springframework.beans.factory.annotation.Autowired;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import jakarta.persistence.criteria.Predicate;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 歌手服务实现类
 */
@Service
public class SingerServiceImpl implements SingerService {

    private static final String HOT_SINGER_CACHE_KEY = "singer:hot_list";
    private static final long CACHE_EXPIRATION_HOURS = 2;
    private static final int HOT_SINGER_LIMIT = 50;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private SingerRepository singerRepository;

    @Autowired
    private SingerTypeRepository singerTypeRepository;

    @Autowired
    private MusicRepository musicRepository;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private UserSingerRepository userSingerRepository;

    @Autowired
    private FileService fileService;

    @Autowired
    private CommentRepository commentRepository;

    @Override
    public SingerDTO getSingerById(Long id) {
        Singer singer = singerRepository.findById(id)
                .orElseThrow(() -> new BusinessException("歌手不存在"));
        return convertToDTO(singer);
    }

    @Override
    public PageResult<SingerDTO> getSingerList(Integer page, Integer size, String keyword, Long typeId) {
        // 构建查询条件
        Specification<Singer> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();

            // 关键字查询
            if (StringUtils.hasText(keyword)) {
                predicates.add(cb.like(root.get("name"), "%" + keyword + "%"));
            }

            // 类型查询
            if (typeId != null) {
                predicates.add(cb.equal(root.get("type").get("id"), typeId));
            }

            return cb.and(predicates.toArray(new Predicate[0]));
        };

        // 执行查询
        Page<Singer> singerPage = singerRepository.findAll(
                spec,
                PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createTime"))
        );

        // 转换为DTO
        List<SingerDTO> singerDTOs = singerPage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());

        return new PageResult<>(
                singerDTOs,
                singerPage.getTotalElements(),
                page,
                size
        );
    }

    @Override
    public List<SingerDTO> getLatestSinger(Integer limit) {
        // 查询最新歌手
        List<Singer> singers = singerRepository.findLatest(PageRequest.of(0, limit));

        // 转换为DTO
        return singers.stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
    }

    @Override
    public List<SingerDTO> getHotSinger(Integer limit) {
        String hotSingerJson = redisTemplate.opsForValue().get(HOT_SINGER_CACHE_KEY);

        if (StringUtils.hasText(hotSingerJson)) {
            try {
                List<SingerDTO> cachedList = objectMapper.readValue(hotSingerJson, new TypeReference<List<SingerDTO>>() {});
                return cachedList.stream().limit(limit).collect(Collectors.toList());
            } catch (Exception e) {
                System.err.println("Failed to deserialize hot singer list from Redis: " + e.getMessage());
            }
        }

        List<SingerDTO> hotList = calculateAndCacheHotSingerSync();
        return hotList.stream().limit(limit).collect(Collectors.toList());
    }

    private List<SingerDTO> calculateAndCacheHotSingerSync() {
        try {
            return updateHotSingerCache().get();
        } catch (Exception e) {
            System.err.println("Failed to calculate hot singer list synchronously: " + e.getMessage());
            List<Singer> singers = singerRepository.findHotSingers(PageRequest.of(0, HOT_SINGER_LIMIT));
            return singers.stream()
                    .map(this::convertToDTO)
                    .collect(Collectors.toList());
        }
    }

    @Scheduled(cron = "0 0 * * * ?")
    public void scheduleUpdateHotSinger() {
        System.out.println("Scheduled task started: Updating hot singer cache...");
        updateHotSingerCache();
    }

    @Async
    @Transactional(readOnly = true)
    public CompletableFuture<List<SingerDTO>> updateHotSingerCache() {
        List<Singer> allSingers = singerRepository.findAll();

        allSingers.parallelStream().forEach(singer -> {
            double score = HotSingerScoreUtil.calculateHotScore(singer);
            singer.setHotScore(score);
        });

        List<SingerDTO> sortedHotSingers = allSingers.stream()
                .sorted(Comparator.comparing(Singer::getHotScore).reversed())
                .limit(HOT_SINGER_LIMIT)
                .map(this::convertToDTO)
                .collect(Collectors.toList());

        try {
            String jsonToCache = objectMapper.writeValueAsString(sortedHotSingers);
            redisTemplate.opsForValue().set(HOT_SINGER_CACHE_KEY, jsonToCache, CACHE_EXPIRATION_HOURS, TimeUnit.HOURS);
            System.out.println("Hot singer cache updated successfully with " + sortedHotSingers.size() + " items.");
        } catch (Exception e) {
            System.err.println("Failed to cache hot singer list to Redis: " + e.getMessage());
        }

        return CompletableFuture.completedFuture(sortedHotSingers);
    }

    @Override
    @Transactional
    public SingerDTO addSinger(SingerDTO singerDTO, MultipartFile avatar) {
        // 检查歌手名称是否已存在
        if (singerRepository.findByName(singerDTO.getName()).isPresent()) {
            throw new BusinessException("歌手名称已存在");
        }

        // 上传头像
        String avatarUrl = null;
        if (avatar != null && !avatar.isEmpty()) {
            String avatarPhysicalPath = fileService.uploadImage(avatar);
            avatarUrl = avatarPhysicalPath.replace(System.getProperty("user.dir") + File.separator + "music-houduan", "").replace(File.separator, "/");
        }

        // 创建歌手实体
        Singer singer = new Singer();
        singer.setName(singerDTO.getName());
        singer.setAvatar(avatarUrl);
        singer.setIntroduction(singerDTO.getIntroduction());
        singer.setCollectCount(0);

        // 设置歌手类型
        if (singerDTO.getTypeId() != null) {
            SingerType type = singerTypeRepository.findById(singerDTO.getTypeId())
                    .orElseThrow(() -> new BusinessException("歌手类型不存在"));
            singer.setType(type);
        }

        // 保存歌手
        Singer savedSinger = singerRepository.save(singer);

        return convertToDTO(savedSinger);
    }

    @Override
    @Transactional
    public SingerDTO updateSinger(Long id, SingerDTO singerDTO, MultipartFile avatar) {
        // 获取歌手
        Singer singer = singerRepository.findById(id)
                .orElseThrow(() -> new BusinessException("歌手不存在"));

        // 检查歌手名称是否已存在
        if (!singer.getName().equals(singerDTO.getName()) &&
                singerRepository.findByName(singerDTO.getName()).isPresent()) {
            throw new BusinessException("歌手名称已存在");
        }

        // 如果有新的头像，则上传并更新URL
        if (avatar != null && !avatar.isEmpty()) {
            // 删除旧头像
            if (singer.getAvatar() != null) {
                fileService.deleteFile(singer.getAvatar());
            }

            // 上传新头像
            String avatarPhysicalPath = fileService.uploadImage(avatar);
            String avatarUrl = avatarPhysicalPath.replace(System.getProperty("user.dir") + File.separator + "music-houduan", "").replace(File.separator, "/");
            singer.setAvatar(avatarUrl);
        }

        // 更新歌手信息
        singer.setName(singerDTO.getName());
        singer.setIntroduction(singerDTO.getIntroduction());

        // 更新歌手类型
        if (singerDTO.getTypeId() != null) {
            SingerType type = singerTypeRepository.findById(singerDTO.getTypeId())
                    .orElseThrow(() -> new BusinessException("歌手类型不存在"));
            singer.setType(type);
        }

        // 保存歌手
        Singer updatedSinger = singerRepository.save(singer);

        return convertToDTO(updatedSinger);
    }

    @Override
    @Transactional
    public boolean deleteSinger(Long id) {
        // 获取歌手
        Singer singer = singerRepository.findById(id)
                .orElseThrow(() -> new BusinessException("歌手不存在"));

        // 删除头像
        if (singer.getAvatar() != null) {
            fileService.deleteFile(singer.getAvatar());
        }

        // 删除歌手
        singerRepository.delete(singer);

        return true;
    }

    @Override
    @Transactional
    public boolean collectSinger(Long id) {
        // 获取当前登录用户
        Long userId = StpUtil.getLoginIdAsLong();
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new BusinessException("用户不存在"));

        // 获取歌手
        Singer singer = singerRepository.findById(id)
                .orElseThrow(() -> new BusinessException("歌手不存在"));

        // 检查是否已收藏
        if (userSingerRepository.existsByUserAndSinger(user, singer)) {
            throw new BusinessException("已收藏该歌手");
        }

        // 收藏歌手
        userSingerRepository.collectSinger(user.getId(), singer.getId());

        // 更新收藏数
        singer.setCollectCount((singer.getCollectCount() == null ? 0 : singer.getCollectCount()) + 1);
        singerRepository.save(singer);

        return true;
    }

    @Override
    @Transactional
    public boolean uncollectSinger(Long id) {
        // 获取当前登录用户
        Long userId = StpUtil.getLoginIdAsLong();
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new BusinessException("用户不存在"));

        // 获取歌手
        Singer singer = singerRepository.findById(id)
                .orElseThrow(() -> new BusinessException("歌手不存在"));

        // 检查是否已收藏
        if (!userSingerRepository.existsByUserAndSinger(user, singer)) {
            throw new BusinessException("未收藏该歌手");
        }

        // 取消收藏歌手
        userSingerRepository.uncollectSinger(user.getId(), singer.getId());

        // 更新收藏数
        singer.setCollectCount((singer.getCollectCount() == null ? 0 : singer.getCollectCount()) - 1);
        singerRepository.save(singer);

        return true;
    }

    @Override
    public boolean isCollected(Long id) {
        if (!StpUtil.isLogin()) {
            return false;
        }
        Long userId = StpUtil.getLoginIdAsLong();
        return userSingerRepository.existsByUserIdAndSingerId(userId, id);
    }

    @Override
    public PageResult<SingerDTO> getCollectedSinger(Integer page, Integer size) {
        // 获取当前登录用户
        Long userId = StpUtil.getLoginIdAsLong();

        // 获取用户收藏的歌手
        Page<Singer> singerPage = userSingerRepository.findCollectedSinger(
                userId,
                PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "collectTime"))
        );

        // 转换为DTO
        List<SingerDTO> singerDTOs = singerPage.getContent().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());

        return new PageResult<>(
                singerDTOs,
                singerPage.getTotalElements(),
                page,
                size
        );
    }

    @Override
    @Transactional
    public boolean addMusicToSinger(Long singerId, Long musicId) {
        // 获取歌手
        Singer singer = singerRepository.findById(singerId)
                .orElseThrow(() -> new BusinessException("歌手不存在"));

        // 获取音乐
        Music music = musicRepository.findById(musicId)
                .orElseThrow(() -> new BusinessException("音乐不存在"));

        // 检查是否已关联（双向检查）
        if (singer.getMusics().contains(music) || music.getSingers().contains(singer)) {
            throw new BusinessException("该音乐已关联到歌手");
        }

        // 添加关联并保存
        singer.getMusics().add(music);
        music.getSingers().add(singer);
        singerRepository.save(singer);

        return true;
    }

    @Override
    @Transactional
    public boolean removeMusicFromSinger(Long singerId, Long musicId) {
        // 获取歌手
        Singer singer = singerRepository.findById(singerId)
                .orElseThrow(() -> new BusinessException("歌手不存在"));

        // 获取音乐
        Music music = musicRepository.findById(musicId)
                .orElseThrow(() -> new BusinessException("音乐不存在"));

        // 检查是否已关联
        if (!singer.getMusics().contains(music)) {
            throw new BusinessException("该音乐未关联到歌手");
        }

        // 移除关联
        singer.getMusics().remove(music);
        music.getSingers().remove(singer);
        singerRepository.save(singer);

        return true;
    }

    @Override
    public Singer getById(Long id) {
        return singerRepository.findById(id)
                .orElseThrow(() -> new BusinessException("歌手不存在"));
    }

    /**
     * 将实体转换为DTO
     *
     * @param singer 歌手实体
     * @return 歌手DTO
     */
    private SingerDTO convertToDTO(Singer singer) {
        SingerDTO singerDTO = new SingerDTO();
        BeanUtils.copyProperties(singer, singerDTO);
        singerDTO.setHotScore(singer.getHotScore());

        // 设置类型信息
        if (singer.getType() != null) {
            singerDTO.setTypeId(singer.getType().getId());
            singerDTO.setTypeName(singer.getType().getName());
        }

        // 设置音乐数量
        Long musicCount = musicRepository.countBySingersContaining(singer);
        singerDTO.setMusicCount(musicCount);

        // 设置粉丝数量
        Long fansCount = userSingerRepository.countBySingerId(singer.getId());
        singerDTO.setFansCount(fansCount);
        singerDTO.setCollectCount(fansCount.intValue());

        // 设置评论数
        long commentCount = commentRepository.countByTargetIdAndTargetType(singer.getId(), 3); // 3 for singer
        singerDTO.setCommentCount((int) commentCount);

        // 检查当前登录用户是否已收藏该歌手
        if (StpUtil.isLogin()) {
            Long userId = StpUtil.getLoginIdAsLong();
            boolean isCollected = userSingerRepository.existsByUserIdAndSingerId(userId, singer.getId());
            singerDTO.setIsCollected(isCollected);
        } else {
            singerDTO.setIsCollected(false);
        }

        // 设置关联音乐列表
        if (singer.getMusics() != null) {
            singerDTO.setMusics(singer.getMusics().stream()
                .map(music -> {
                    MusicDTO musicDTO = new MusicDTO();
                    BeanUtils.copyProperties(music, musicDTO);
                    return musicDTO;
                })
                .collect(Collectors.toList()));
        }

        return singerDTO;
    }
}
