package com.ycwl.basic.facebody.adapter;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.facebody.model.v20191230.AddFaceEntityRequest;
import com.aliyuncs.facebody.model.v20191230.AddFaceRequest;
import com.aliyuncs.facebody.model.v20191230.AddFaceResponse;
import com.aliyuncs.facebody.model.v20191230.CreateFaceDbRequest;
import com.aliyuncs.facebody.model.v20191230.DeleteFaceDbRequest;
import com.aliyuncs.facebody.model.v20191230.DeleteFaceEntityRequest;
import com.aliyuncs.facebody.model.v20191230.ListFaceDbsRequest;
import com.aliyuncs.facebody.model.v20191230.ListFaceDbsResponse;
import com.aliyuncs.facebody.model.v20191230.ListFaceEntitiesRequest;
import com.aliyuncs.facebody.model.v20191230.ListFaceEntitiesResponse;
import com.aliyuncs.facebody.model.v20191230.SearchFaceRequest;
import com.aliyuncs.facebody.model.v20191230.SearchFaceResponse;
import com.aliyuncs.profile.DefaultProfile;
import com.ycwl.basic.facebody.entity.AddFaceResp;
import com.ycwl.basic.facebody.entity.AliFaceBodyConfig;
import com.ycwl.basic.facebody.entity.SearchFaceResp;
import com.ycwl.basic.facebody.entity.SearchFaceResultItem;
import com.ycwl.basic.utils.ratelimiter.FixedRateLimiter;
import com.ycwl.basic.utils.ratelimiter.IRateLimiter;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
public class AliFaceBodyAdapter implements IFaceBodyAdapter {
    private static final Map<String, IRateLimiter> addEntityLimiters = new ConcurrentHashMap<>();
    private static final Map<String, IRateLimiter> addFaceLimiters = new ConcurrentHashMap<>();
    private static final Map<String, IRateLimiter> addDbLimiters = new ConcurrentHashMap<>();
    private static final Map<String, IRateLimiter> listDbLimiters = new ConcurrentHashMap<>();
    private static final Map<String, IRateLimiter> listFaceLimiters = new ConcurrentHashMap<>();
    private static final Map<String, IRateLimiter> searchFaceLimiters = new ConcurrentHashMap<>();
    private static final Map<String, IRateLimiter> deleteDbLimiters = new ConcurrentHashMap<>();
    private static final Map<String, IRateLimiter> deleteEntityLimiters = new ConcurrentHashMap<>();

    private AliFaceBodyConfig config;

    public boolean setConfig(AliFaceBodyConfig config) {
        this.config = config;
        return true;
    }

    @Override
    public boolean loadConfig(Map<String, String> _config) {
        AliFaceBodyConfig config = new AliFaceBodyConfig();
        config.setAccessKeyId(_config.get("accessKeyId"));
        config.setAccessKeySecret(_config.get("accessKeySecret"));
        config.setRegion(_config.get("region"));
        this.config = config;
        return true;
    }

    private IRateLimiter getLimiter(LOCK_TYPE type) {
        return switch (type) {
            case ADD_DB ->
                    addDbLimiters.computeIfAbsent(config.getAccessKeyId(), k -> new FixedRateLimiter(600, TimeUnit.MILLISECONDS));
            case ADD_ENTITY ->
                    addEntityLimiters.computeIfAbsent(config.getAccessKeyId(), k -> new FixedRateLimiter(600, TimeUnit.MILLISECONDS));
            case ADD_FACE ->
                    addFaceLimiters.computeIfAbsent(config.getAccessKeyId(), k -> new FixedRateLimiter(600, TimeUnit.MILLISECONDS));
            case LIST_DB ->
                    listDbLimiters.computeIfAbsent(config.getAccessKeyId(), k -> new FixedRateLimiter(500, TimeUnit.MILLISECONDS));
            case LIST_FACE -> listFaceLimiters.computeIfAbsent(config.getAccessKeyId(), k -> new FixedRateLimiter(2));
            case SEARCH_FACE ->
                    searchFaceLimiters.computeIfAbsent(config.getAccessKeyId(), k -> new FixedRateLimiter(200, TimeUnit.MILLISECONDS));
            case DELETE_DB ->
                    deleteDbLimiters.computeIfAbsent(config.getAccessKeyId(), k -> new FixedRateLimiter(600, TimeUnit.MILLISECONDS));
            case DELETE_ENTITY ->
                    deleteEntityLimiters.computeIfAbsent(config.getAccessKeyId(), k -> new FixedRateLimiter(600, TimeUnit.MILLISECONDS));
            default -> new FixedRateLimiter(600, TimeUnit.MILLISECONDS);
        };
    }

    @Override
    public boolean addFaceDb(String dbName) {
        IRateLimiter addDbLimiter = getLimiter(LOCK_TYPE.ADD_DB);
        try (ClientWrapper clientWrapper = getClient()) {
            IAcsClient client = clientWrapper.client();
            CreateFaceDbRequest request = new CreateFaceDbRequest();
            request.setName(dbName);
            try {
                addDbLimiter.acquire();
            } catch (InterruptedException ignored) {
            }
            client.getAcsResponse(request);
            return true;
        } catch (ClientException e) {
            log.error("阿里云添加人脸数据库失败！", e);
            return false;
        }
    }

    @Override
    public boolean deleteFaceDb(String dbName) {
        ListFaceEntitiesRequest request = new ListFaceEntitiesRequest();
        IRateLimiter deleteEntityLimiter = getLimiter(LOCK_TYPE.DELETE_ENTITY);
        IRateLimiter deleteDbLimiter = getLimiter(LOCK_TYPE.DELETE_DB);
        request.setDbName(dbName);
        request.setOrder("asc");
        request.setLimit(200);
        try (ClientWrapper clientWrapper = getClient()) {
            IAcsClient client = clientWrapper.client();
            while (true) {
                ListFaceEntitiesResponse response = client.getAcsResponse(request);
                if (response.getData().getTotalCount() == 0) {
                    break;
                }
                response.getData().getEntities().forEach(entity -> {
                    DeleteFaceEntityRequest deleteFaceEntityRequest = new DeleteFaceEntityRequest();
                    deleteFaceEntityRequest.setDbName(entity.getDbName());
                    deleteFaceEntityRequest.setEntityId(entity.getEntityId());
                    try {
                        deleteEntityLimiter.acquire();
                    } catch (InterruptedException ignored) {
                    }
                    try {
                        client.getAcsResponse(deleteFaceEntityRequest);
                    } catch (ClientException e) {
                        log.error("删除人脸数据失败！", e);
                    }
                });
            }
            DeleteFaceDbRequest deleteFaceDbRequest = new DeleteFaceDbRequest();
            deleteFaceDbRequest.setName(dbName);
            try {
                deleteDbLimiter.acquire();
            } catch (InterruptedException ignored) {
            }
            client.getAcsResponse(deleteFaceDbRequest);
        } catch (ClientException e) {
            log.error("删除人脸数据库失败！", e);
            return false;
        }
        return true;
    }

    @Override
    public List<String> listFaceDb() {
        ListFaceDbsRequest request = new ListFaceDbsRequest();
        try (ClientWrapper clientWrapper = getClient()) {
            IAcsClient client = clientWrapper.client();
            ListFaceDbsResponse response = client.getAcsResponse(request);
            return response.getData().getDbList().stream().map(ListFaceDbsResponse.Data.DbListItem::getName).collect(Collectors.toList());
        } catch (ClientException e) {
            log.error("获取人脸数据库失败！", e);
            return Collections.emptyList();
        }
    }

    @Override
    public AddFaceResp addFace(String dbName, String entityId, String faceUrl, String extData) {
        IRateLimiter addEntityLimiter = getLimiter(LOCK_TYPE.ADD_ENTITY);
        IRateLimiter addFaceLimiter = getLimiter(LOCK_TYPE.ADD_FACE);
        AddFaceEntityRequest request = new AddFaceEntityRequest();
        request.setDbName(dbName);
        request.setEntityId(entityId);
        try (ClientWrapper clientWrapper = getClient()) {
            IAcsClient client = clientWrapper.client();
            try {
                addEntityLimiter.acquire();
            } catch (InterruptedException ignored) {
            }
            try {
                client.getAcsResponse(request);
            } catch (ClientException e) {
                log.error("addFaceEntity, {}/{}", dbName, entityId, e);
                return null;
            }
            AddFaceRequest addFaceRequest = new AddFaceRequest();
            addFaceRequest.setDbName(dbName);
            addFaceRequest.setEntityId(entityId);
            addFaceRequest.setImageUrl(faceUrl);
            addFaceRequest.setExtraData(extData);
            AddFaceResp respVo = new AddFaceResp();
            try {
                addFaceLimiter.acquire();
            } catch (InterruptedException ignored) {
            }
            try {
                AddFaceResponse acsResponse = client.getAcsResponse(addFaceRequest);
                respVo.setScore(acsResponse.getData().getQualitieScore());
                return respVo;
            } catch (ClientException e) {
                log.error("addFace, {}/{}", dbName, entityId, e);
                return null;
            }
        }
    }

    @Override
    public boolean deleteFace(String dbName, String entityId) {
        IRateLimiter deleteEntityLimiter = getLimiter(LOCK_TYPE.DELETE_ENTITY);
        DeleteFaceEntityRequest request = new DeleteFaceEntityRequest();
        request.setDbName(dbName);
        request.setEntityId(entityId);
        try (ClientWrapper clientWrapper = getClient()) {
            IAcsClient client = clientWrapper.client();
            try {
                deleteEntityLimiter.acquire();
            } catch (InterruptedException ignored) {
            }
            try {
                client.getAcsResponse(request);
                return true;
            } catch (ClientException e) {
                log.error("删除人脸数据失败！", e);
                return false;
            }
        }
    }

    @Override
    public List<String> listFace(String dbName, String prefix, Integer offset, Integer size) {
        IRateLimiter listFaceLimiter = getLimiter(LOCK_TYPE.LIST_FACE);
        ListFaceEntitiesRequest listFaceEntitiesRequest = new ListFaceEntitiesRequest();
        listFaceEntitiesRequest.setDbName(dbName);
        listFaceEntitiesRequest.setOrder("asc");
        if (offset != null) {
            listFaceEntitiesRequest.setOffset(offset);
        }
        listFaceEntitiesRequest.setLimit(Objects.requireNonNullElse(size, 200));
        if (StringUtils.isNotEmpty(prefix)) {
            listFaceEntitiesRequest.setEntityIdPrefix(prefix);
        }
        try (ClientWrapper clientWrapper = getClient()) {
            IAcsClient client = clientWrapper.client();
            try {
                listFaceLimiter.acquire();
            } catch (InterruptedException ignored) {
            }
            try {
                ListFaceEntitiesResponse response = client.getAcsResponse(listFaceEntitiesRequest);
                return response.getData().getEntities().stream().map(ListFaceEntitiesResponse.Data.Entity::getEntityId).collect(Collectors.toList());
            } catch (ClientException e) {
                log.error("获取人脸数据失败！", e);
                return Collections.emptyList();
            }
        }
    }

    @Override
    public SearchFaceResp searchFace(String dbName, String faceUrl) {
        SearchFaceResp resp = new SearchFaceResp();
        IRateLimiter searchFaceLimiter = getLimiter(LOCK_TYPE.SEARCH_FACE);
        try (ClientWrapper clientWrapper = getClient()) {
            IAcsClient client = clientWrapper.client();
            SearchFaceRequest request = new SearchFaceRequest();
            request.setDbName(dbName);
            request.setImageUrl(faceUrl);
            request.setLimit(100);
            try {
                searchFaceLimiter.acquire();
            } catch (InterruptedException ignored) {
            }
            try {
                SearchFaceResponse response = client.getAcsResponse(request);
                List<SearchFaceResponse.Data.MatchListItem> matchList = response.getData().getMatchList();
                if (matchList.isEmpty()) {
                    resp.setOriginalFaceScore(0f);
                    return resp;
                }
                SearchFaceResponse.Data.MatchListItem matchItem = matchList.getFirst();
                resp.setOriginalFaceScore(matchItem.getQualitieScore());
                resp.setResult(matchItem.getFaceItems().stream().map(item -> {
                    SearchFaceResultItem resultItem = new SearchFaceResultItem();
                    resultItem.setDbName(dbName);
                    resultItem.setFaceId(item.getFaceId());
                    resultItem.setExtData(item.getExtraData());
                    resultItem.setScore(item.getScore());
                    return resultItem;
                }).collect(Collectors.toList()));
                if (!resp.getResult().isEmpty()) {
                    resp.setFirstMatchRate(resp.getResult().getFirst().getScore());
                }
                return resp;
            } catch (ClientException e) {
                log.error("搜索人脸失败！", e);
                return null;
            }
        }
    }

    public ClientWrapper getClient() {
        DefaultProfile profile = DefaultProfile.getProfile(
                config.getRegion(), config.getAccessKeyId(), config.getAccessKeySecret());
        IAcsClient client = new DefaultAcsClient(profile);
        return new ClientWrapper(client);
    }

    public record ClientWrapper(IAcsClient client) implements AutoCloseable {

        @Override
        public void close() {
            if (client == null) {
                return;
            }
            client.shutdown();
        }
    }
    protected enum LOCK_TYPE {
        ADD_DB,
        ADD_ENTITY,
        ADD_FACE,
        LIST_DB,
        LIST_FACE,
        SEARCH_FACE,
        DELETE_DB,
        DELETE_ENTITY,
    }
}
