package com.magistr.app.service;

import com.magistr.app.model.TimeSlot;
import com.magistr.app.model.TimeSlotDateAssignment;
import com.magistr.app.model.TimeSlotScope;
import com.magistr.app.repository.TimeSlotDateAssignmentRepository;
import com.magistr.app.repository.TimeSlotRepository;
import com.magistr.app.repository.TimeSlotScopeRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Service
public class EffectiveTimeSlotService {

    private static final String APPLY_MODE_DEFAULT = "DEFAULT";
    private static final String APPLY_MODE_WEEKDAY = "WEEKDAY";

    private final TimeSlotRepository timeSlotRepository;
    private final TimeSlotScopeRepository timeSlotScopeRepository;
    private final TimeSlotDateAssignmentRepository dateAssignmentRepository;

    public EffectiveTimeSlotService(TimeSlotRepository timeSlotRepository,
                                    TimeSlotScopeRepository timeSlotScopeRepository,
                                    TimeSlotDateAssignmentRepository dateAssignmentRepository) {
        this.timeSlotRepository = timeSlotRepository;
        this.timeSlotScopeRepository = timeSlotScopeRepository;
        this.dateAssignmentRepository = dateAssignmentRepository;
    }

    @Transactional(readOnly = true)
    public List<TimeSlot> findEffective(LocalDate date) {
        if (date == null) {
            throw new IllegalArgumentException("Дата временной сетки обязательна");
        }
        TimeSlotScope defaultScope = defaultScope();
        TimeSlotScope effectiveScope = effectiveScope(date, defaultScope);
        List<TimeSlot> defaultSlots = timeSlotRepository
                .findAllByTimeSlotScopeIdOrderByOrderNumberAsc(defaultScope.getId());
        if (Objects.equals(effectiveScope.getId(), defaultScope.getId())) {
            return defaultSlots;
        }

        Map<Integer, TimeSlot> slotsByOrder = new LinkedHashMap<>();
        defaultSlots.forEach(slot -> slotsByOrder.put(slot.getOrderNumber(), slot));
        timeSlotRepository.findAllByTimeSlotScopeIdOrderByOrderNumberAsc(effectiveScope.getId())
                .forEach(slot -> slotsByOrder.put(slot.getOrderNumber(), slot));
        return new ArrayList<>(slotsByOrder.values());
    }

    @Transactional(readOnly = true)
    public boolean isEffectiveOnDate(Long timeSlotId, LocalDate date) {
        return timeSlotId != null && findEffective(date).stream()
                .anyMatch(slot -> Objects.equals(slot.getId(), timeSlotId));
    }

    private TimeSlotScope effectiveScope(LocalDate date, TimeSlotScope defaultScope) {
        return dateAssignmentRepository.findByDate(date)
                .map(TimeSlotDateAssignment::getTimeSlotScope)
                .or(() -> timeSlotScopeRepository.findByApplyModeAndDayOfWeek(
                        APPLY_MODE_WEEKDAY,
                        date.getDayOfWeek().getValue()))
                .orElse(defaultScope);
    }

    private TimeSlotScope defaultScope() {
        return timeSlotScopeRepository.findFirstByApplyModeOrderByDisplayOrderAsc(APPLY_MODE_DEFAULT)
                .orElseThrow(() -> new IllegalStateException("Базовая сетка времени не настроена"));
    }
}
