package com.ycwl.basic.service.task.impl;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.facebody.model.v20191230.SearchFaceRequest;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.constant.FaceConstant;
import com.ycwl.basic.exception.BaseException;
import com.ycwl.basic.facebody.FaceBodyFactory;
import com.ycwl.basic.facebody.adapter.AliFaceBodyAdapter;
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
import com.ycwl.basic.facebody.entity.SearchFaceResp;
import com.ycwl.basic.facebody.entity.SearchFaceResultItem;
import com.ycwl.basic.mapper.FaceDetectLogMapper;
import com.ycwl.basic.mapper.FaceMapper;
import com.ycwl.basic.mapper.FaceSampleMapper;
import com.ycwl.basic.mapper.SourceMapper;
import com.ycwl.basic.model.mobile.order.IsBuyRespVO;
import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity;
import com.ycwl.basic.model.pc.device.entity.DeviceEntity;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.model.pc.faceDetectLog.entity.FaceDetectLog;
import com.ycwl.basic.model.pc.faceDetectLog.resp.MatchLocalRecord;
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
import com.ycwl.basic.repository.DeviceRepository;
import com.ycwl.basic.repository.FaceRepository;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.service.task.TaskFaceService;
import com.ycwl.basic.task.VideoPieceGetter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.aliyuncs.IAcsClient;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.ycwl.basic.constant.FaceConstant.USER_FACE_DB_NAME;

@Slf4j
@Service
public class TaskFaceServiceImpl implements TaskFaceService {
    @Autowired
    private FaceMapper faceMapper;
    @Autowired
    private FaceSampleMapper faceSampleMapper;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private FaceDetectLogMapper logMapper;
    @Autowired
    private FaceRepository faceRepository;
    @Autowired
    private SourceMapper sourceMapper;
    @Autowired
    private OrderBiz orderBiz;

    @Autowired
    private ScenicRepository scenicRepository;
    @Autowired
    private DeviceRepository deviceRepository;
    @Autowired
    @Lazy
    private ScenicService scenicService;

    private IAcsClient getClient() {
        AliFaceBodyAdapter use = (AliFaceBodyAdapter) FaceBodyFactory.use();
        return use.getClient().client();
    }

    @Override
    public SearchFaceRespVo searchFace(Long faceId) {
        FaceRespVO faceRespVO = faceMapper.getById(faceId);
        if (faceRespVO == null) {
            SearchFaceRespVo vo = new SearchFaceRespVo();
            vo.setSampleListIds(new ArrayList<>());
            return vo;
        }
        Long scenicId = faceRespVO.getScenicId();
        ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
        IFaceBodyAdapter faceBodyAdapter;
        if (scenicConfig != null && scenicConfig.getFaceType() != null) {
            faceBodyAdapter = FaceBodyFactory.getAdapter(scenicConfig.getFaceType());
            faceBodyAdapter.loadConfig(JSONObject.parseObject(scenicConfig.getFaceConfigJson(), Map.class));
        } else {
            faceBodyAdapter = FaceBodyFactory.use();
        }
        SearchFaceRespVo respVo = searchFace(faceBodyAdapter, scenicId.toString(), faceRespVO.getFaceUrl(), "系统定时任务检索");
        if (respVo != null) {
            FaceEntity faceEntity = new FaceEntity();
            faceEntity.setId(faceId);
            faceEntity.setMatchResult(respVo.getSearchResultJson());
            faceEntity.setFirstMatchRate(BigDecimal.valueOf(respVo.getFirstMatchRate()));
            faceEntity.setScore(respVo.getScore());
            faceEntity.setMatchSampleIds(StringUtils.join(respVo.getSampleListIds(), ","));
            faceMapper.update(faceEntity);
            faceRepository.clearFaceCache(faceId);
            List<Long> sampleListIds = respVo.getSampleListIds();
            if (sampleListIds != null && !sampleListIds.isEmpty()) {// 匹配原片：照片
                List<SourceEntity> sourceEntities = sourceMapper.listBySampleIds(sampleListIds);
                List<MemberSourceEntity> memberSourceEntityList = sourceEntities.stream().map(sourceEntity -> {
                    DeviceConfigEntity deviceConfig = deviceRepository.getDeviceConfig(sourceEntity.getDeviceId());
                    MemberSourceEntity memberSourceEntity = new MemberSourceEntity();
                    memberSourceEntity.setScenicId(scenicId);
                    memberSourceEntity.setFaceId(faceEntity.getId());
                    memberSourceEntity.setMemberId(faceRespVO.getMemberId());
                    memberSourceEntity.setSourceId(sourceEntity.getId());
                    memberSourceEntity.setType(sourceEntity.getType());
                    IsBuyRespVO isBuy = orderBiz.isBuy(faceRespVO.getMemberId(), scenicId, sourceEntity.getType(), sourceEntity.getId());
                    if (isBuy.isBuy()) { // 如果用户买过
                        memberSourceEntity.setIsBuy(1);
                    } else if (isBuy.isFree()) { // 全免费逻辑
                        memberSourceEntity.setIsBuy(1);
                    } else {
                        memberSourceEntity.setIsBuy(0);
                    }
                    memberSourceEntity.setIsFree(0);
                    if (deviceConfig != null) {
                        if (sourceEntity.getType() == 1) {
                            if (Integer.valueOf(1).equals(deviceConfig.getVideoFree())) {
                                memberSourceEntity.setIsFree(1);
                            }
                        } else if (sourceEntity.getType() == 2) {
                            if (Integer.valueOf(1).equals(deviceConfig.getImageFree())) {
                                memberSourceEntity.setIsFree(1);
                            }
                        }
                    }
                    return memberSourceEntity;
                }).collect(Collectors.toList());
                if (!memberSourceEntityList.isEmpty()) {
                    sourceMapper.addRelations(memberSourceEntityList);
                    VideoPieceGetter.Task task = new VideoPieceGetter.Task();
                    task.faceId = faceEntity.getId();
                    task.faceSampleIds = sampleListIds;
                    task.memberId = faceRespVO.getMemberId();
                    VideoPieceGetter.addTask(task);
                }
            }
        }
        return respVo;
    }

    @Override
    public SearchFaceRespVo searchFace(IFaceBodyAdapter adapter, String dbName, String faceUrl, String reason) {
        assureFaceDb(adapter, dbName);
        SearchFaceRequest request = new SearchFaceRequest();
        request.setDbName(dbName);
        request.setImageUrl(faceUrl);
        request.setLimit(200);
//        request.setQualityScoreThreshold(60f);
        FaceDetectLog logEntity = FaceDetectLog.quickCreate(reason, request);
        float threshold = 0;
        int tourMinutes = -1;
        if (StringUtils.isNumeric(dbName)) {
            ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(Long.valueOf(dbName));
            if (scenicConfig != null) {
                if (scenicConfig.getFaceScoreThreshold() != null) {
                    threshold = scenicConfig.getFaceScoreThreshold() / 100F;
                }
                if (scenicConfig.getTourTime() != null) {
                    tourMinutes = scenicConfig.getTourTime();
                }
            }
        } else if (StringUtils.isNumeric(dbName.replace(USER_FACE_DB_NAME, ""))) {
            Long scenicId = Long.valueOf(dbName.replace(USER_FACE_DB_NAME, ""));
            ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
            if (scenicConfig != null) {
                if (scenicConfig.getFaceScoreThreshold() != null) {
                    threshold = scenicConfig.getFaceScoreThreshold() / 100F;
                }
            }
        }
        final float _threshold = threshold;
        List<Long> acceptFaceSampleIds;
        List<Long> allFaceSampleIds;
        try {
            SearchFaceResp response = adapter.searchFace(dbName, faceUrl);
            if (response == null) {
                return null;
            }
            logEntity.fillResponse(response);
            SearchFaceRespVo respVo = new SearchFaceRespVo();
            List<SearchFaceResultItem> records = response.getResult();
            respVo.setScore(response.getOriginalFaceScore());
            respVo.setSearchResultJson(JSON.toJSONString(records));
            logEntity.setMatchRawRecord(records);
            if (records.isEmpty()) {
                return respVo;
            }
            acceptFaceSampleIds = records.stream()
                    .filter(record -> record.getScore() > _threshold)
                    .map(SearchFaceResultItem::getExtData)
                    .filter(StringUtils::isNumeric)
                    .map(Long::valueOf)
                    .collect(Collectors.toList());
            allFaceSampleIds = records.stream()
                    .map(SearchFaceResultItem::getExtData)
                    .filter(StringUtils::isNumeric)
                    .map(Long::valueOf)
                    .collect(Collectors.toList());
            List<FaceSampleEntity> allFaceSampleList = new ArrayList<>();
            if (StringUtils.isNumeric(dbName)) { // 景区
                allFaceSampleList = faceSampleMapper.listByIds(allFaceSampleIds);
                if (!acceptFaceSampleIds.isEmpty()) {
                    Long firstFaceSampleId = acceptFaceSampleIds.getFirst();
                    Optional<FaceSampleEntity> firstFaceSample = allFaceSampleList.stream().filter(faceSample -> faceSample.getId().equals(firstFaceSampleId)).findAny();
                    if (firstFaceSample.isPresent()) {
                        if (tourMinutes > 0) {
                            List<FaceSampleEntity> acceptFaceSampleList = faceSampleMapper.listByIds(acceptFaceSampleIds);
                            Date startDate = DateUtil.offsetMinute(firstFaceSample.get().getCreateAt(), -tourMinutes/2);
                            Date endDate = DateUtil.offsetMinute(firstFaceSample.get().getCreateAt(), tourMinutes/2);
                            acceptFaceSampleIds = acceptFaceSampleList.stream()
                                    .filter(faceSample -> faceSample.getCreateAt().after(startDate) && faceSample.getCreateAt().before(endDate))
                                    .map(FaceSampleEntity::getId)
                                    .collect(Collectors.toList());
                            log.info("时间范围逻辑：最高匹配：{}，时间范围需要在：{}~{}间", firstFaceSample, startDate, endDate);
                        } else {
                            log.info("时间范围逻辑：景区未限制");
                        }
                    } else {
                        log.info("时间范围逻辑：最高匹配ID：{}，未找到", firstFaceSampleId);
                    }
                }
            }
            List<MatchLocalRecord> collect = new ArrayList<>();
            for (SearchFaceResultItem item : records) {
                MatchLocalRecord record = new MatchLocalRecord();
                record.setIdStr(item.getExtData());
                record.setFaceSampleId(Long.parseLong(item.getExtData()));
                if (StringUtils.isNumeric(item.getDbName())) {
                    Optional<FaceSampleEntity> optionalFse = allFaceSampleList.stream().filter(face -> face.getId().equals(Long.parseLong(item.getExtData()))).findAny();
                    if (optionalFse.isPresent()) {
                        DeviceEntity device = deviceRepository.getDevice(optionalFse.get().getDeviceId());
                        if (device != null) {
                            record.setDeviceName(device.getName());
                        }
                        record.setAccept(acceptFaceSampleIds.contains(optionalFse.get().getId()));
                        record.setFaceUrl(optionalFse.get().getFaceUrl());
                        record.setShotDate(optionalFse.get().getCreateAt());
                    }
                } else {
                    record.setFaceUrl(getFaceUrl(record.getFaceSampleId()));
                }
                record.setScore(item.getScore());
                record.setMatched(item.getScore() > _threshold);
                collect.add(record);
            }
            logEntity.setMatchLocalRecord(JSONObject.toJSONString(collect));
            if (acceptFaceSampleIds.isEmpty()) {
                respVo.setFirstMatchRate(0f);
                respVo.setSampleListIds(Collections.emptyList());
                return respVo;
            }
            respVo.setFirstMatchRate(response.getFirstMatchRate());
            respVo.setSampleListIds(acceptFaceSampleIds);
            return respVo;
        } catch (Exception e) {
            logEntity.setMatchRawResult("识别错误，错误为：["+e.getLocalizedMessage()+"]");
            throw new BaseException(e.getMessage());
        } finally {
            logMapper.insert(logEntity);
        }
    }

    @Override
    public boolean deleteFaceSample(Long scenicId, String dbName, String entityId) {
        ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
        IFaceBodyAdapter adapter = scenicService.getScenicFaceBodyAdapter(scenicId);
        log.info("删除{}人脸实体：{}", dbName, entityId);
        try {
            adapter.deleteFace(dbName, entityId);
            return true;
        } catch (Exception e) {
            log.error("删除人脸样本失败！", e);
            return false;
        }
    }

    @Override
    public boolean assureFaceDb(IFaceBodyAdapter faceBodyAdapter, String dbName) {
        if (redisTemplate.hasKey(FaceConstant.FACE_DB_NAME_PFX + dbName)) {
            return true;
        }
        try {
            if (!faceBodyAdapter.assureFaceDb(dbName)) {
                log.info("创建人脸库失败！");
                removeFaceDBCache(dbName);
                return false;
            }
            addFaceDBCache(dbName);
            return true;
        } catch (Exception e) {
            log.error("创建人脸库失败！", e);
            removeFaceDBCache(dbName);
            return false;
        }
    }

    public void addFaceDBCache(String dbName) {
        redisTemplate.opsForValue().set(FaceConstant.FACE_DB_NAME_PFX + dbName, "1", 2, TimeUnit.HOURS);
    }

    public Boolean removeFaceDBCache(String dbName) {
        return redisTemplate.delete(FaceConstant.FACE_DB_NAME_PFX + dbName);
    }

    public Boolean clearFaceDBCache() {
        return redisTemplate.delete(FaceConstant.FACE_DB_NAME_PFX + "*");
    }

    public String getFaceUrl(Long faceId) {
        if (faceId == null) {
            return null;
        }
        if (redisTemplate.hasKey(FaceConstant.FACE_USER_URL_PFX + faceId)) {
            return redisTemplate.opsForValue().get(FaceConstant.FACE_USER_URL_PFX + faceId);
        }
        FaceRespVO faceRespVO = faceMapper.getById(faceId);
        if (faceRespVO == null) {
            return null;
        }
        String faceUrl = faceRespVO.getFaceUrl();
        if (StringUtils.isNotBlank(faceUrl)) {
            addFaceUrlCache(faceId, faceUrl);
        }
        return faceUrl;
    }
    public void addFaceUrlCache(Long faceId, String faceUrl) {
        if (faceId == null) {
            return;
        }
        if (StringUtils.isBlank(faceUrl)) {
            return;
        }
        redisTemplate.opsForValue().set(FaceConstant.FACE_USER_URL_PFX + faceId, faceUrl, 3, TimeUnit.DAYS);
    }
}
