package com.yuchen.saas.park.service.service.door.config.impl;

import com.google.common.collect.Sets;
import com.yuchen.saas.park.api.commons.domain.DoorAccessControlConfigIssueTaskContext;
import com.yuchen.saas.park.api.commons.domain.DoorAccessControlConfigTarget;
import com.yuchen.saas.park.api.commons.entity.SimpleEntity;
import com.yuchen.saas.park.api.commons.enums.DoorAccessControlConfigType;
import com.yuchen.saas.park.api.entity.door.config.DoorAccessControlConfigIssueTask;
import com.yuchen.saas.park.api.entity.door.config.DoorAccessControlConfigIssueTaskDetails;
import com.yuchen.saas.park.api.entity.door.config.DoorAccessControlTimeConfigDetails;
import com.yuchen.saas.park.api.util.ImageUtils;
import com.yuchen.saas.park.service.mapper.door.adapter.AdapterRepository;
import com.yuchen.saas.park.service.mapper.door.card.DoorAccessControlCardRepository;
import com.yuchen.saas.park.service.mapper.door.config.DoorAccessControlConfigIssueTaskDetailsRepository;
import com.yuchen.saas.park.service.mapper.door.config.DoorAccessControlConfigIssueTaskRepository;
import com.yuchen.saas.park.service.mapper.door.config.DoorAccessControlTimeConfigDetailsRepository;
import com.yuchen.saas.park.service.service.adapter.AdapterComponent;
import com.yuchen.saas.park.service.service.domain.task.TaskContext;
import com.yuchen.saas.park.service.service.domain.task.TaskListContext;
import com.yuchen.saas.park.service.service.door.communication.DoorAccessControlCommandService;
import com.yuchen.saas.park.service.service.door.communication.domain.CallResult;
import com.yuchen.saas.park.service.service.door.config.DoorAccessControlConfigIssueTaskComponent;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Nullable;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Positive;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;

import static com.yuchen.saas.park.api.commons.enums.DoorAccessControlConfigState.*;
import static com.yuchen.saas.park.api.commons.enums.DoorAccessControlConfigType.*;
import static java.util.Comparator.comparing;
import static java.util.Optional.ofNullable;
import static java.util.function.UnaryOperator.identity;
import static java.util.stream.Collectors.*;

@Transactional
@Component
@Slf4j
@RequiredArgsConstructor
public class DoorAccessControlConfigIssueTaskComponentImpl implements DoorAccessControlConfigIssueTaskComponent {
    private final DoorAccessControlTimeConfigDetailsRepository doorAccessControlTimeConfigDetailsRepository;
    private final DoorAccessControlCardRepository doorAccessControlCardRepository;

    private final DoorAccessControlConfigIssueTaskRepository doorAccessControlConfigIssueTaskRepository;
    private final DoorAccessControlConfigIssueTaskDetailsRepository doorAccessControlConfigIssueTaskDetailsRepository;

    private final AdapterRepository adapterRepository;
    private final AdapterComponent adapterComponent;

    private final DoorAccessControlCommandService doorAccessControlCommandService;

    @Positive
    @Override
    public long saveTaskAndDetails(DoorAccessControlConfigIssueTaskContext o) {
        val target = o.getTarget();
        val entry = o.getEntry();

        val exists = doorAccessControlConfigIssueTaskRepository.getByTargetAndEntry(target, entry).orElse(null);

        if (exists == null) {
            val task = DoorAccessControlConfigIssueTask.from(o);
            doorAccessControlConfigIssueTaskRepository.insert(task);

            val taskDetails = new DoorAccessControlConfigIssueTaskDetails().setDoorAccessControlConfigIssueTaskId(task.getId());
            doorAccessControlConfigIssueTaskDetailsRepository.insert(taskDetails);

            return task.getId();
        }

        if (Objects.equals(exists.getContext(), o) && exists.isSuccessful()) {
            log.debug("context not change -> ignore and skip");
            return exists.getId();
        }

        exists.setConfigValue(o.getConfigValue()).setDisabled(o.isDisabled()).setProcessed(false).setSuccessful(false);
        doorAccessControlConfigIssueTaskRepository.updateById(exists);

        val taskDetails = new DoorAccessControlConfigIssueTaskDetails().setDoorAccessControlConfigIssueTaskId(exists.getId());
        doorAccessControlConfigIssueTaskDetailsRepository.insert(taskDetails);

        return exists.getId();
    }

    @Override
    public void saveUserSchedule(DoorAccessControlConfigTarget target, List<DoorAccessControlConfigIssueTask> exists) {
        val list = doorAccessControlTimeConfigDetailsRepository.listByTarget(target);

        val activatedList = list.stream()
          .collect(groupingBy(DoorAccessControlTimeConfigDetails::getTimeConfigId))
          .values()
          .stream()
          .map(o -> o.stream().max(comparing(DoorAccessControlTimeConfigDetails::getUpdateTime)).orElseThrow())
          .collect(toList());

        val ids = activatedList.stream()
          .map(this::activeAndHandleState)
          .map(o -> DoorAccessControlConfigIssueTaskContext.ofUser(o.getTarget(), o.getTimeConfigId(), this.getTimeConfigNoById(o.getTimeConfigId()), o.getState() != EFFECTIVE))
          .map(this::saveTaskAndDetails)
          .collect(toSet());

        val activatedIds = activatedList.stream().map(SimpleEntity::getId).collect(toSet());

        // 更新未激活的门禁访问控制时间配置为非激活状态
        list.stream()
          .filter(o -> !activatedIds.contains(o.getId()))
          .filter(DoorAccessControlTimeConfigDetails::isActivated)
          .map(o -> o.setActivated(false))
          .forEach(doorAccessControlTimeConfigDetailsRepository::updateById);


        //this.disableExists(exists, USER, ids);
        //更新IssueTask
        this.updateIssueTask(exists);
    }

    @Override
    public void saveCardSchedule(DoorAccessControlConfigTarget target, List<DoorAccessControlConfigIssueTask> exists) {
        val ids = doorAccessControlCardRepository.listByUserId(target.getUserId())
          .stream()
          .filter(o -> !o.isLossReport())
          .map(o -> DoorAccessControlConfigIssueTaskContext.ofCard(target, o.getId(), o.getNo()))
          .map(this::saveTaskAndDetails)
          .collect(toSet());

        this.disableExists(exists, CARD, ids);
        //更新IssueTask
        this.updateIssueTask(exists);
    }

    @Override
    public void saveFacialSchedule(DoorAccessControlConfigTarget target, List<DoorAccessControlConfigIssueTask> exists) {
        val ids = adapterRepository.getFacialByUserId(target.getUserId())
          .stream()
          .map(ImageUtils::getBase64StringFromUrl)
          .map(o -> DoorAccessControlConfigIssueTaskContext.ofFacial(target, o))
          .map(this::saveTaskAndDetails)
          .collect(toSet());

        this.disableExists(exists, FACIAL, ids);
        //更新IssueTask
        this.updateIssueTask(exists);
    }

    @Override
    public void handleUserTask(TaskListContext context) {
        this.handleTaskInternal(context, this::handleUserTask);
    }

    @Override
    public void handleCardTask(TaskListContext context) {
        this.handleTaskInternal(context, this::handleCardTask);
    }

    @Override
    public void handleFacialTask(TaskListContext context) {
        this.handleTaskInternal(context, this::handleFacialTask);
    }

    private void disableExists(List<DoorAccessControlConfigIssueTask> exists, DoorAccessControlConfigType type, Set<@Positive Long> excludedIds) {
        // 通过流操作筛选出类型为type的元素，并将其收集到Set集合中
        val pending = exists.stream().filter(o -> o.getType() == type).collect(toSet());

        // 使用日志记录器打印出pending集合、excludedIds集合和它们的差集
        log.debug("=== exists: {}, excluded: {} -> diff: {}", pending.stream().map(SimpleEntity::getId).collect(toSet()), excludedIds, Sets.difference(pending, excludedIds));

        // 筛选出不在excludedIds集合中的元素，并且满足isDisabled()和isSuccessful()条件的元素
        pending.stream()
          .filter(o -> !excludedIds.contains(o.getId()))
          .filter(o -> o.isDisabled() && o.isSuccessful())
          .forEach(o -> doorAccessControlConfigIssueTaskRepository.updateById(o.setDisabled(true).setSuccessful(false)));

    }

    /**
     * 更新IssueTask状态
     * @param exists
     */
    private void updateIssueTask(List<DoorAccessControlConfigIssueTask> exists) {
        val pending = exists.stream().collect(toSet());
        pending.stream().forEach(o -> doorAccessControlConfigIssueTaskRepository.updateById(o.setDisabled(true).setSuccessful(false).setProcessed(false)));

    }

    private DoorAccessControlTimeConfigDetails activeAndHandleState(DoorAccessControlTimeConfigDetails o) {
        val activated = o.isActivated();

        val effectiveStartProcessed = o.isEffectiveStartProcessed();
        val effectiveEndProcessed = o.isEffectiveEndProcessed();
        val state = o.getState();

        this.handleState(o);
        o.setActivated(true);

        log.debug("activated: {} -> {}", activated, o.isActivated());
        log.debug("state: {} -> {}", state, o.getState());
        log.debug("startProcessed: {} -> {}", effectiveStartProcessed, o.isEffectiveStartProcessed());
        log.debug("endProcessed: {} -> {}", effectiveEndProcessed, o.isEffectiveEndProcessed());

        if (Objects.equals(activated, o.isActivated()) && Objects.equals(state, o.getState()) && Objects.equals(effectiveStartProcessed, o.isEffectiveStartProcessed()) && Objects.equals(effectiveEndProcessed, o.isEffectiveEndProcessed())) {
            return o;
        }

        doorAccessControlTimeConfigDetailsRepository.updateById(o);
        return o;
    }

    private void handleState(DoorAccessControlTimeConfigDetails o) {
        log.debug("{}: {} -> {}", o.getId(), o.getUpdateTime(), o.getTarget());

        if (o.isEffectiveStartProcessed() && o.isEffectiveEndProcessed()) {
            return;
        }

        val effectiveStartTime = o.getEffectiveStartTime();
        val effectiveEndTime = o.getEffectiveEndTime();
        val now = LocalDateTime.now();

        if (effectiveStartTime == null || !effectiveStartTime.isAfter(now)) {
            o.setEffectiveStartProcessed(true);
        }

        if (effectiveEndTime == null || !effectiveEndTime.isAfter(now)) {
            o.setEffectiveEndProcessed(true);
        }

        if (effectiveStartTime != null && now.isBefore(effectiveStartTime)) {
            o.setState(INIT);
        } else if (effectiveEndTime != null && now.isAfter(effectiveEndTime)) {
            o.setState(EXPIRED);
        } else {
            o.setState(EFFECTIVE);
        }
    }

    private void handleUserTask(@NotEmpty List<TaskContext> list) {
        val configNos = list.stream().map(TaskContext::getTask).filter(Objects::nonNull).filter(DoorAccessControlConfigIssueTask::isEnabled).map(DoorAccessControlConfigIssueTask::getConfigValue).map(Long::parseLong).collect(toSet());

        val target = list.stream().findFirst().map(TaskContext::getTask).map(DoorAccessControlConfigIssueTask::getTarget).orElseThrow();

        val result = doorAccessControlCommandService.addUser(target, configNos);
        //更新调用结果
        list.forEach(o -> this.updateResult(o, result));
    }

    @Positive
    private void handleCardTask(@NotEmpty List<TaskContext> list) {
        for (val o : list) {
            val task = o.getTask();

            CallResult result;
            if (task.isEnabled()) {
                result = doorAccessControlCommandService.addCard(task.getTarget(), task.getConfigValue());
            } else {
                result = doorAccessControlCommandService.deleteCard(task.getTarget(), task.getConfigValue());
            }

            this.updateResult(o, result);
        }
    }

    private void handleFacialTask(@NotEmpty List<TaskContext> list) {
        for (val o : list) {
            val task = o.getTask();

            CallResult result;
            if (task.isEnabled()) {
                result = doorAccessControlCommandService.addUserFacial(task.getTarget(), task.getConfigValue());
            } else {
                result = CallResult.error("不支持删除人脸操作");
            }

            this.updateResult(o, result);
        }
    }

    @Nullable
    private String getTimeConfigNoById(@Positive long id) {
        return ofNullable(adapterComponent.listAllDoorAccessControlTemplateIdNo().get(id)).map(String::valueOf).orElse(null);
    }

    /**
     * 处理任务的内部方法。
     *
     * @param context 任务列表上下文，包含任务的详细信息和配置。
     * @param consumer 消费者接口，用于处理经过筛选后的任务列表。
     */
    private void handleTaskInternal(TaskListContext context, Consumer<@NotEmpty List<TaskContext>> consumer) {
        // 获取任务详情并启动每个任务
        val details = context.getDetails();
        details.forEach(DoorAccessControlConfigIssueTaskDetails::start);

        // 将任务详情映射到其ID，并保留最新的任务实例
        val taskDetailsMap = details.stream().collect(toMap(DoorAccessControlConfigIssueTaskDetails::getDoorAccessControlConfigIssueTaskId, identity(), BinaryOperator.maxBy(comparing(DoorAccessControlConfigIssueTaskDetails::getId))));

        // 获取待处理的任务详情
        val pendingDetails = taskDetailsMap.values();

        // 取消不再待处理的任务，并更新数据库
        details.stream().filter(o -> !pendingDetails.contains(o)).map(DoorAccessControlConfigIssueTaskDetails::cancel).forEach(doorAccessControlConfigIssueTaskDetailsRepository::updateById);

        // 根据任务ID和对应的详情，构建任务上下文列表
        val list = context.getTasks().stream().map(o -> TaskContext.of(o, taskDetailsMap.get(o.getId()))).collect(toList());

        // 设置未处理或缺少配置值的任务为处理中且不成功，并更新数据库
        list.stream().filter(o -> o.getDetails() == null).map(o -> o.getTask().setProcessed(true).setSuccessful(false)).forEach(doorAccessControlConfigIssueTaskRepository::updateById);

        list.stream().filter(o -> o.getTask().getConfigValue() == null).forEach(o -> {
            doorAccessControlConfigIssueTaskRepository.updateById(o.getTask().setProcessed(true).setSuccessful(false));
            doorAccessControlConfigIssueTaskDetailsRepository.updateById(o.getDetails().setCanceled(true).setProcessingFailureReason("对应的参数不存在"));
        });

        // 如果存在有效的任务，则通过消费者接口处理这些任务
        Optional.of(list.stream().filter(o -> o.getDetails() != null).filter(o -> o.getTask().getConfigValue() != null).collect(toList()))
          .filter(o -> !o.isEmpty())
          .ifPresent(consumer);
    }

    private void updateResult(TaskContext context, CallResult result) {
        val successful = result.isSuccessful();

        val task = context.getTask();

        doorAccessControlConfigIssueTaskRepository.updateById(task.setProcessed(true).setSuccessful(successful));

        val details = context.getDetails();
        details.setSuccessful(successful).setProcessingEndTime(LocalDateTime.now()).setProcessingFailureReason(result.getFailureDetails());
        doorAccessControlConfigIssueTaskDetailsRepository.updateById(details);
    }
}
