package com.starry.admin.modules.clerk.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.enums.ClerkMediaReviewState;
import com.starry.admin.modules.clerk.enums.ClerkMediaUsage;
import com.starry.admin.modules.clerk.mapper.PlayClerkMediaAssetMapper;
import com.starry.admin.modules.clerk.module.entity.PlayClerkMediaAssetEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkMediaAssetService;
import com.starry.admin.modules.media.entity.PlayMediaEntity;
import com.starry.admin.modules.media.service.IPlayMediaService;
import com.starry.common.utils.IdUtils;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class PlayClerkMediaAssetServiceImpl extends ServiceImpl<PlayClerkMediaAssetMapper, PlayClerkMediaAssetEntity>
        implements IPlayClerkMediaAssetService {

    @Resource
    private IPlayMediaService mediaService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PlayClerkMediaAssetEntity linkDraftAsset(String tenantId, String clerkId, String mediaId,
            ClerkMediaUsage usage) {
        LambdaQueryWrapper<PlayClerkMediaAssetEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(PlayClerkMediaAssetEntity::getClerkId, clerkId)
                .eq(StrUtil.isNotBlank(tenantId), PlayClerkMediaAssetEntity::getTenantId, tenantId)
                .eq(PlayClerkMediaAssetEntity::getUsage, usage.getCode())
                .eq(PlayClerkMediaAssetEntity::getMediaId, mediaId);
        PlayClerkMediaAssetEntity existing = this.getOne(wrapper, false);
        if (existing != null) {
            if (StrUtil.isBlank(existing.getTenantId()) && StrUtil.isNotBlank(tenantId)) {
                existing.setTenantId(tenantId);
            }
            if (Boolean.TRUE.equals(existing.getDeleted())) {
                existing.setDeleted(false);
            }
            existing.setReviewState(ClerkMediaReviewState.DRAFT.getCode());
            if (existing.getOrderIndex() == null) {
                existing.setOrderIndex(resolveNextOrderIndex(clerkId, usage));
            }
            this.updateById(existing);
            return existing;
        }

        PlayClerkMediaAssetEntity entity = new PlayClerkMediaAssetEntity();
        entity.setId(IdUtils.getUuid());
        entity.setClerkId(clerkId);
        entity.setTenantId(tenantId);
        entity.setMediaId(mediaId);
        entity.setUsage(usage.getCode());
        entity.setReviewState(ClerkMediaReviewState.DRAFT.getCode());
        entity.setOrderIndex(resolveNextOrderIndex(clerkId, usage));
        entity.setDeleted(false);
        this.save(entity);
        return entity;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitWithOrder(String clerkId, ClerkMediaUsage usage, List<String> mediaIds) {
        List<String> ordered = distinctMediaIds(mediaIds);
        List<PlayClerkMediaAssetEntity> assets = listActiveByUsage(clerkId, usage);
        if (CollectionUtil.isEmpty(assets)) {
            return;
        }
        Map<String, PlayClerkMediaAssetEntity> assetsByMediaId = assets.stream()
                .collect(Collectors.toMap(PlayClerkMediaAssetEntity::getMediaId, item -> item));
        List<PlayClerkMediaAssetEntity> updates = new ArrayList<>();
        int order = 0;
        for (String mediaId : ordered) {
            PlayClerkMediaAssetEntity asset = assetsByMediaId.get(mediaId);
            if (asset == null) {
                continue;
            }
            asset.setOrderIndex(order++);
            asset.setReviewState(ClerkMediaReviewState.PENDING.getCode());
            asset.setSubmittedTime(LocalDateTime.now());
            updates.add(asset);
        }

        Set<String> keepSet = ordered.stream().collect(Collectors.toSet());
        for (PlayClerkMediaAssetEntity asset : assets) {
            if (!keepSet.contains(asset.getMediaId())) {
                asset.setReviewState(ClerkMediaReviewState.REJECTED.getCode());
                asset.setOrderIndex(0);
                updates.add(asset);
            }
        }
        if (CollectionUtil.isNotEmpty(updates)) {
            this.updateBatchById(updates);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void reorder(String clerkId, ClerkMediaUsage usage, List<String> mediaIds) {
        List<String> ordered = distinctMediaIds(mediaIds);
        if (CollectionUtil.isEmpty(ordered)) {
            return;
        }
        List<PlayClerkMediaAssetEntity> assets = listActiveByUsage(clerkId, usage);
        if (CollectionUtil.isEmpty(assets)) {
            return;
        }
        Map<String, PlayClerkMediaAssetEntity> assetsByMediaId = assets.stream()
                .collect(Collectors.toMap(PlayClerkMediaAssetEntity::getMediaId, item -> item));
        List<PlayClerkMediaAssetEntity> updates = new ArrayList<>();
        int order = 0;
        for (String mediaId : ordered) {
            PlayClerkMediaAssetEntity asset = assetsByMediaId.get(mediaId);
            if (asset == null) {
                continue;
            }
            if (!Objects.equals(asset.getOrderIndex(), order)) {
                asset.setOrderIndex(order);
                updates.add(asset);
            }
            order++;
        }
        if (CollectionUtil.isNotEmpty(updates)) {
            this.updateBatchById(updates);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void softDelete(String clerkId, String mediaId) {
        LambdaQueryWrapper<PlayClerkMediaAssetEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(PlayClerkMediaAssetEntity::getClerkId, clerkId)
                .eq(PlayClerkMediaAssetEntity::getMediaId, mediaId)
                .eq(PlayClerkMediaAssetEntity::getDeleted, false);
        PlayClerkMediaAssetEntity asset = this.getOne(wrapper, false);
        if (asset == null) {
            return;
        }
        asset.setDeleted(true);
        asset.setReviewState(ClerkMediaReviewState.REJECTED.getCode());
        this.updateById(asset);
    }

    @Override
    public List<PlayClerkMediaAssetEntity> listByState(String clerkId, ClerkMediaUsage usage,
            Collection<ClerkMediaReviewState> states) {
        LambdaQueryWrapper<PlayClerkMediaAssetEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(PlayClerkMediaAssetEntity::getClerkId, clerkId)
                .eq(PlayClerkMediaAssetEntity::getUsage, usage.getCode())
                .eq(PlayClerkMediaAssetEntity::getDeleted, false)
                .orderByAsc(PlayClerkMediaAssetEntity::getOrderIndex)
                .orderByDesc(PlayClerkMediaAssetEntity::getCreatedTime);
        if (CollectionUtil.isNotEmpty(states)) {
            wrapper.in(PlayClerkMediaAssetEntity::getReviewState,
                    states.stream().map(ClerkMediaReviewState::getCode).collect(Collectors.toList()));
        }
        return this.list(wrapper);
    }

    @Override
    public List<PlayClerkMediaAssetEntity> listActiveByUsage(String clerkId, ClerkMediaUsage usage) {
        LambdaQueryWrapper<PlayClerkMediaAssetEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(PlayClerkMediaAssetEntity::getClerkId, clerkId)
                .eq(PlayClerkMediaAssetEntity::getUsage, usage.getCode())
                .eq(PlayClerkMediaAssetEntity::getDeleted, false)
                .orderByAsc(PlayClerkMediaAssetEntity::getOrderIndex)
                .orderByDesc(PlayClerkMediaAssetEntity::getCreatedTime);
        return this.list(wrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void applyReviewDecision(String clerkId, ClerkMediaUsage usage, List<String> approvedValues,
            String reviewRecordId, String note) {
        List<PlayClerkMediaAssetEntity> assets = listActiveByUsage(clerkId, usage);
        if (CollectionUtil.isEmpty(assets)) {
            return;
        }
        List<String> normalized = distinctMediaIds(approvedValues);
        Map<String, PlayClerkMediaAssetEntity> byMediaId = assets.stream()
                .collect(Collectors.toMap(PlayClerkMediaAssetEntity::getMediaId, item -> item));
        Map<String, PlayClerkMediaAssetEntity> byUrl = buildAssetByUrlMap(assets);

        List<PlayClerkMediaAssetEntity> updates = new ArrayList<>();
        Set<String> approvedAssetIds = new java.util.HashSet<>();
        int order = 0;
        for (String value : normalized) {
            PlayClerkMediaAssetEntity asset = byMediaId.get(value);
            if (asset == null) {
                asset = byUrl.get(value);
            }
            if (asset == null) {
                continue;
            }
            asset.setReviewState(ClerkMediaReviewState.APPROVED.getCode());
            asset.setOrderIndex(order++);
            asset.setReviewRecordId(reviewRecordId);
            if (StrUtil.isNotBlank(note)) {
                asset.setNote(note);
            }
            updates.add(asset);
            approvedAssetIds.add(asset.getId());
        }

        for (PlayClerkMediaAssetEntity asset : assets) {
            if (approvedAssetIds.contains(asset.getId())) {
                continue;
            }
            asset.setReviewState(ClerkMediaReviewState.REJECTED.getCode());
            asset.setReviewRecordId(reviewRecordId);
            updates.add(asset);
        }

        if (CollectionUtil.isNotEmpty(updates)) {
            this.updateBatchById(updates);
        }
    }

    private int resolveNextOrderIndex(String clerkId, ClerkMediaUsage usage) {
        LambdaQueryWrapper<PlayClerkMediaAssetEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(PlayClerkMediaAssetEntity::getClerkId, clerkId)
                .eq(PlayClerkMediaAssetEntity::getUsage, usage.getCode())
                .eq(PlayClerkMediaAssetEntity::getDeleted, false)
                .orderByDesc(PlayClerkMediaAssetEntity::getOrderIndex)
                .last("limit 1");
        PlayClerkMediaAssetEntity last = this.getOne(wrapper, false);
        if (last == null || last.getOrderIndex() == null) {
            return 0;
        }
        return last.getOrderIndex() + 1;
    }

    private List<String> distinctMediaIds(List<String> mediaIds) {
        if (CollectionUtil.isEmpty(mediaIds)) {
            return Collections.emptyList();
        }
        return mediaIds.stream()
                .filter(StrUtil::isNotBlank)
                .map(String::trim)
                .distinct()
                .collect(Collectors.toList());
    }

    private Map<String, PlayClerkMediaAssetEntity> buildAssetByUrlMap(List<PlayClerkMediaAssetEntity> assets) {
        if (CollectionUtil.isEmpty(assets)) {
            return Collections.emptyMap();
        }
        List<String> mediaIds = assets.stream().map(PlayClerkMediaAssetEntity::getMediaId).collect(Collectors.toList());
        List<PlayMediaEntity> mediaList = mediaService.listByIds(mediaIds);
        if (CollectionUtil.isEmpty(mediaList)) {
            return Collections.emptyMap();
        }
        Map<String, String> mediaIdToUrl = mediaList.stream()
                .filter(item -> StrUtil.isNotBlank(item.getUrl()))
                .collect(Collectors.toMap(PlayMediaEntity::getId, PlayMediaEntity::getUrl, (left, right) -> left));
        Map<String, PlayClerkMediaAssetEntity> map = new HashMap<>();
        for (PlayClerkMediaAssetEntity asset : assets) {
            String url = mediaIdToUrl.get(asset.getMediaId());
            if (StrUtil.isNotBlank(url)) {
                map.put(url, asset);
            }
        }
        return map;
    }

}
