package de.votian.services.service;

import de.votian.services.dto.PublicHolidayDayDto;
import de.votian.services.dto.PublicHolidayYearDto;
import de.votian.services.entity.PublicHoliday;
import de.votian.services.repository.PublicHolidayRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

@Service
@Transactional
public class PublicHolidayService {

    private static final DateTimeFormatter ISO_DATE = DateTimeFormatter.ISO_LOCAL_DATE;

    private final PublicHolidayRepository publicHolidayRepository;

    public PublicHolidayService(PublicHolidayRepository publicHolidayRepository) {
        this.publicHolidayRepository = publicHolidayRepository;
    }

    @Transactional(readOnly = true)
    public PublicHolidayYearDto getYear(Long headquartersId, int year) {
        LocalDate today = LocalDate.now();
        int selectedYear = year > 0 ? year : today.getYear();

        List<PublicHoliday> entries = publicHolidayRepository
                .findByHeadquartersIdAndYearValueOrderByMonthValueAscDayValueAscTimeFromAsc(headquartersId, selectedYear);
        Map<String, PublicHoliday> entriesByDay = new LinkedHashMap<>();
        for (PublicHoliday entry : entries) {
            entriesByDay.putIfAbsent(key(entry.getMonthValue(), entry.getDayValue()), entry);
        }

        TreeSet<Integer> availableYears = new TreeSet<>(publicHolidayRepository.findDistinctYearsByHeadquartersId(headquartersId));
        availableYears.add(today.getYear());
        availableYears.add(today.getYear() + 1);

        PublicHolidayYearDto dto = new PublicHolidayYearDto();
        dto.setHeadquartersId(headquartersId);
        dto.setYear(selectedYear);
        dto.setEditable(selectedYear >= today.getYear());
        dto.setAvailableYears(new ArrayList<>(availableYears));
        dto.setCopySourceYears(availableYears.stream()
                .filter(candidate -> candidate < selectedYear)
                .toList());

        List<PublicHolidayDayDto> days = new ArrayList<>();
        LocalDate date = LocalDate.of(selectedYear, 1, 1);
        LocalDate end = date.withMonth(12).withDayOfMonth(31);
        while (!date.isAfter(end)) {
            PublicHoliday entry = entriesByDay.get(key(date.getMonthValue(), date.getDayOfMonth()));
            PublicHolidayDayDto dayDto = new PublicHolidayDayDto();
            dayDto.setIsoDate(date.format(ISO_DATE));
            dayDto.setMonth(date.getMonthValue());
            dayDto.setDay(date.getDayOfMonth());
            dayDto.setWeekdayName(weekdayName(date.getDayOfWeek()));
            dayDto.setHoliday(entry != null);
            dayDto.setName(entry != null && entry.getName() != null ? entry.getName() : "");
            dayDto.setEditable(date.isAfter(today));
            days.add(dayDto);
            date = date.plusDays(1);
        }
        dto.setDays(days);
        return dto;
    }

    public PublicHolidayYearDto saveYear(Long headquartersId, int year, List<PublicHolidayDayDto> requestedDays) {
        LocalDate today = LocalDate.now();
        if (year < today.getYear()) {
            throw new IllegalArgumentException("Vergangene Jahre koennen nicht geaendert werden.");
        }

        List<PublicHoliday> existingEntries = publicHolidayRepository
                .findByHeadquartersIdAndYearValueOrderByMonthValueAscDayValueAscTimeFromAsc(headquartersId, year);

        List<PublicHoliday> entriesToPersist = new ArrayList<>();
        if (year == today.getYear()) {
            for (PublicHoliday entry : existingEntries) {
                LocalDate entryDate = LocalDate.of(year, entry.getMonthValue(), entry.getDayValue());
                if (!entryDate.isAfter(today)) {
                    entriesToPersist.add(entry);
                }
            }
        }

        Map<String, PublicHolidayDayDto> requestedByDay = new LinkedHashMap<>();
        if (requestedDays != null) {
            for (PublicHolidayDayDto requestedDay : requestedDays) {
                if (requestedDay.getMonth() == null || requestedDay.getDay() == null) {
                    continue;
                }
                requestedByDay.put(key(requestedDay.getMonth(), requestedDay.getDay()), requestedDay);
            }
        }

        for (PublicHolidayDayDto requestedDay : requestedByDay.values()) {
            LocalDate date = LocalDate.of(year, requestedDay.getMonth(), requestedDay.getDay());
            if (!requestedDay.isHoliday() || !date.isAfter(today)) {
                continue;
            }
            PublicHoliday entry = new PublicHoliday();
            entry.setHeadquartersId(headquartersId);
            entry.setYearValue(year);
            entry.setMonthValue(requestedDay.getMonth());
            entry.setDayValue(requestedDay.getDay());
            entry.setName(requestedDay.getName() != null ? requestedDay.getName().trim() : "");
            entriesToPersist.add(entry);
        }

        entriesToPersist.sort(Comparator
                .comparing(PublicHoliday::getMonthValue)
                .thenComparing(PublicHoliday::getDayValue));

        publicHolidayRepository.deleteByHeadquartersIdAndYearValue(headquartersId, year);
        publicHolidayRepository.saveAll(entriesToPersist);
        return getYear(headquartersId, year);
    }

    private String key(Integer month, Integer day) {
        return month + "-" + day;
    }

    private String weekdayName(DayOfWeek dayOfWeek) {
        return switch (dayOfWeek) {
            case MONDAY -> "Montag";
            case TUESDAY -> "Dienstag";
            case WEDNESDAY -> "Mittwoch";
            case THURSDAY -> "Donnerstag";
            case FRIDAY -> "Freitag";
            case SATURDAY -> "Samstag";
            case SUNDAY -> "Sonntag";
        };
    }
}
