package com.aps.service.impl;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.aps.common.util.SnowFlackIdWorker;
import com.aps.entity.EquipCapacityDef;
import com.aps.entity.EquipShiftCapacity;
import com.aps.entity.ProdEquipSpecialCal;
import com.aps.mapper.ProdEquipSpecialCalMapper;
import com.aps.service.EquipCapacityDefService;
import com.aps.service.EquipShiftCapacityService;
import com.aps.service.ProdEquipSpecialCalService;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class ProdEquipSpecialCalServiceImpl extends ServiceImpl<ProdEquipSpecialCalMapper, ProdEquipSpecialCal>
        implements ProdEquipSpecialCalService {

    private static final int BATCH_SIZE = 100;
    private static final DateTimeFormatter TIME_PERIOD_FORMATTER =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Autowired
    private EquipCapacityDefService equipCapacityDefService;

    @Autowired
    private EquipShiftCapacityService equipShiftCapacityService;

    @Override
    public void copyFromEquipCapacityDef(String sceneId) {
        List<EquipCapacityDef> activeCapacityDefs = equipCapacityDefService.lambdaQuery()
                .eq(EquipCapacityDef::getIsDeleted, 0)
                .isNotNull(EquipCapacityDef::getReferenceId)
                .isNotNull(EquipCapacityDef::getEquipId)
                .list();

        List<ProdEquipSpecialCal> specialCals = activeCapacityDefs.stream()
                .map(capacityDef -> convertToProdEquipSpecialCal(capacityDef, sceneId))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        saveBatchIfNeeded(specialCals);
    }

    @Override
    public void copyFromEquipShiftCapacity(String sceneId) {
        copyFromEquipShiftCapacity(sceneId, null);
    }

    @Override
    public void copyFromEquipShiftCapacity(String sceneId, Collection<Long> planResourceIds) {
        List<EquipShiftCapacity> activeCapacities = queryEquipShiftCapacities(planResourceIds);
        List<ProdEquipSpecialCal> specialCals = activeCapacities.stream()
                .flatMap(capacity -> convertToProdEquipSpecialCals(capacity, sceneId).stream())
                .collect(Collectors.toList());
        saveBatchIfNeeded(specialCals);
    }

    @Override
    public void copyCalendarWithFallback(String sceneId, Collection<Long> planResourceIds) {
        if (planResourceIds == null || planResourceIds.isEmpty()) {
            copyFromEquipShiftCapacity(sceneId);
            return;
        }

        List<EquipShiftCapacity> shiftCapacities = queryEquipShiftCapacities(planResourceIds);
        List<ProdEquipSpecialCal> shiftCapacityCals = shiftCapacities.stream()
                .flatMap(capacity -> convertToProdEquipSpecialCals(capacity, sceneId).stream())
                .collect(Collectors.toList());
        saveBatchIfNeeded(shiftCapacityCals);

        Set<Long> planResourcesWithShiftCapacity = shiftCapacities.stream()
                .map(EquipShiftCapacity::getPlanResourceId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());

        Set<Long> fallbackPlanResourceIds = new HashSet<>(planResourceIds);
        fallbackPlanResourceIds.removeAll(planResourcesWithShiftCapacity);
        if (fallbackPlanResourceIds.isEmpty()) {
            return;
        }

        List<ProdEquipSpecialCal> fallbackCals = equipCapacityDefService.lambdaQuery()
                .eq(EquipCapacityDef::getIsDeleted, 0)
                .isNotNull(EquipCapacityDef::getReferenceId)
                .isNotNull(EquipCapacityDef::getEquipId)
                .in(EquipCapacityDef::getPlanResourceId, fallbackPlanResourceIds)
                .list()
                .stream()
                .map(capacityDef -> convertToProdEquipSpecialCal(capacityDef, sceneId))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        saveBatchIfNeeded(fallbackCals);
    }

    private List<EquipShiftCapacity> queryEquipShiftCapacities(Collection<Long> planResourceIds) {
        LambdaQueryChainWrapper<EquipShiftCapacity> query = equipShiftCapacityService.lambdaQuery()
                .eq(EquipShiftCapacity::getIsDeleted, 0)
                .isNotNull(EquipShiftCapacity::getPlanResourceId)
                .isNotNull(EquipShiftCapacity::getEquipId);

        if (planResourceIds != null && !planResourceIds.isEmpty()) {
            query.in(EquipShiftCapacity::getPlanResourceId, planResourceIds);
        }

        return query.list();
    }

    private ProdEquipSpecialCal convertToProdEquipSpecialCal(EquipCapacityDef capacityDef, String sceneId) {
        try {
            ProdEquipSpecialCal specialCal = new ProdEquipSpecialCal();
            specialCal.setId(String.valueOf(SnowFlackIdWorker.getId()));
            specialCal.setSceneId(sceneId);
            specialCal.setEquipId(capacityDef.getEquipId() != null ? capacityDef.getEquipId().intValue() : null);
            specialCal.setEquipCode(capacityDef.getEquipCode());
            specialCal.setStartDate(capacityDef.getEffectiveStartTime());
            specialCal.setEndDate(capacityDef.getEffectiveEndTime());
            specialCal.setPlanResourceId(capacityDef.getPlanResourceId());
            specialCal.setEfficiencyCoeff(capacityDef.getEfficiencyCoeff() != null
                    ? capacityDef.getEfficiencyCoeff().doubleValue()
                    : 1);
            specialCal.setReferenceId(capacityDef.getReferenceId());
            specialCal.setReferenceName(capacityDef.getReferenceName());
            specialCal.setCreatorUserId(capacityDef.getCreatorUserId());
            specialCal.setCreationTime(LocalDateTime.now());
            specialCal.setReferenceType(capacityDef.getReferenceType());
            return specialCal;
        } catch (Exception e) {
            log.error("Convert EquipCapacityDef to ProdEquipSpecialCal failed", e);
            return null;
        }
    }

    private List<ProdEquipSpecialCal> convertToProdEquipSpecialCals(EquipShiftCapacity capacity, String sceneId) {
        List<TimePeriod> periods = parseTimePeriods(capacity);
        List<ProdEquipSpecialCal> result = new ArrayList<>();

        for (TimePeriod period : periods) {
            ProdEquipSpecialCal specialCal = new ProdEquipSpecialCal();
            specialCal.setId(String.valueOf(SnowFlackIdWorker.getId()));
            specialCal.setSceneId(sceneId);
            specialCal.setEquipId(capacity.getEquipId() != null ? capacity.getEquipId().intValue() : null);
            specialCal.setEquipCode(capacity.getEquipCode());
            specialCal.setStartDate(period.startTime);
            specialCal.setEndDate(period.endTime);
            specialCal.setPlanResourceId(capacity.getPlanResourceId());
            specialCal.setEfficiencyCoeff(capacity.getEfficiencyCoeff() != null ? capacity.getEfficiencyCoeff() : 1);
            specialCal.setCreatorUserId(capacity.getCreatorUserId());
            specialCal.setCreationTime(LocalDateTime.now());
            applyReference(capacity, specialCal);
            result.add(specialCal);
        }

        return result;
    }

    private List<TimePeriod> parseTimePeriods(EquipShiftCapacity capacity) {
        String periodsJson = firstNotBlank(capacity.getValidTimePeriods(),
                capacity.getShiftTimePeriods(),
                capacity.getSpecialTimePeriods(),
                capacity.getHolidayTimePeriods());
        List<TimePeriod> periods = parseTimePeriods(periodsJson);

        if (periods.isEmpty() && capacity.getCapacityDate() != null) {
            periods.add(new TimePeriod(capacity.getCapacityDate(),
                    capacity.getCapacityDate().plusDays(1).minusSeconds(1)));
        }

        return periods;
    }

    private List<TimePeriod> parseTimePeriods(String periodsJson) {
        List<TimePeriod> periods = new ArrayList<>();
        if (periodsJson == null || periodsJson.trim().isEmpty()) {
            return periods;
        }

        try {
            JSONArray array = JSONArray.parseArray(periodsJson);
            for (int i = 0; i < array.size(); i++) {
                JSONObject item = array.getJSONObject(i);
                LocalDateTime startTime = parseDateTime(item.getString("StartTime"));
                LocalDateTime endTime = parseDateTime(item.getString("EndTime"));
                if (startTime != null && endTime != null && startTime.isBefore(endTime)) {
                    periods.add(new TimePeriod(startTime, endTime));
                }
            }
        } catch (Exception e) {
            log.warn("Parse equip shift capacity time periods failed: " + periodsJson + ", " + e.getMessage());
        }

        return periods;
    }

    private void applyReference(EquipShiftCapacity capacity, ProdEquipSpecialCal specialCal) {
        if (capacity.getShiftId() != null) {
            specialCal.setReferenceType(1);
            specialCal.setReferenceId(capacity.getShiftId());
            specialCal.setReferenceName(capacity.getShiftName());
            specialCal.setPeriodDesc(capacity.getShiftName());
            return;
        }
        if (capacity.getHolidayId() != null) {
            specialCal.setReferenceType(2);
            specialCal.setReferenceId(capacity.getHolidayId());
            specialCal.setReferenceName(capacity.getHolidayName());
            specialCal.setPeriodDesc(capacity.getHolidayName());
            return;
        }
        if (capacity.getSpecialId() != null) {
            specialCal.setReferenceType(3);
            specialCal.setReferenceId(capacity.getSpecialId());
            specialCal.setReferenceName(capacity.getSpecialName());
            specialCal.setPeriodDesc(capacity.getSpecialName());
        }
    }

    private LocalDateTime parseDateTime(String value) {
        if (value == null || value.trim().isEmpty()) {
            return null;
        }
        return LocalDateTime.parse(value.trim(), TIME_PERIOD_FORMATTER);
    }

    private String firstNotBlank(String... values) {
        for (String value : values) {
            if (value != null && !value.trim().isEmpty()) {
                return value;
            }
        }
        return null;
    }

    private void saveBatchIfNeeded(List<ProdEquipSpecialCal> specialCals) {
        if (specialCals == null || specialCals.isEmpty()) {
            return;
        }
        for (int i = 0; i < specialCals.size(); i += BATCH_SIZE) {
            int end = Math.min(i + BATCH_SIZE, specialCals.size());
            this.saveBatch(specialCals.subList(i, end), BATCH_SIZE);
        }
    }

    private static class TimePeriod {
        private final LocalDateTime startTime;
        private final LocalDateTime endTime;

        private TimePeriod(LocalDateTime startTime, LocalDateTime endTime) {
            this.startTime = startTime;
            this.endTime = endTime;
        }
    }
}
