package ch.zhaw.gartenverwaltung.types;

import javafx.scene.image.Image;

import java.time.LocalDate;
import java.time.MonthDay;
import java.util.List;
import java.util.stream.Collectors;

import static java.time.temporal.ChronoUnit.DAYS;

public record Plant(
        long id,
        String name,
        String description,
        Image image,
        String spacing,
        int light,
        String soil,
        List<Pest> pests,
        List<GrowthPhase> lifecycle) {

    /**
     * remove all growthPhase which do not belong to the hardiness zone
     * @param zone hardiness zone
     */
    public void inZone(HardinessZone zone) {
        lifecycle.removeIf(growthPhase -> !growthPhase.zone().equals(zone));
    }

    /**
     * get all growthPhases of lifecycle group
     * @param group lifecycle group
     * @return list of growthPhases
     */
    public List<GrowthPhase> lifecycleForGroup(int group) {
        return lifecycle.stream()
                .filter(growthPhase -> growthPhase.group() == group)
                .collect(Collectors.toList());
    }

    /**
     * get sow date from given harvest day from lifecycle group
     * @param harvestDate date of the harvest
     * @return LocaleDate of sow date
     */
    public LocalDate sowDateFromHarvestDate(LocalDate harvestDate) {
        return harvestDate.minusDays(timeToHarvest(lifecycleGroupFromHarvestDate(harvestDate)));
    }

    /**
     * calculate the days between sow and harvest day for lifecycle group
     * @param group the lifecycle group
     * @return Integer number of dates between sow and harvest day
     */
    public int timeToHarvest(int group) {
        List<GrowthPhase> activeLifecycle = lifecycleForGroup(group);
        GrowthPhase sow = activeLifecycle.stream()
                .filter(growthPhase -> !growthPhase.type().equals(GrowthPhaseType.SOW))
                .findFirst()
                .orElseThrow();
        GrowthPhase harvest = activeLifecycle.stream()
                .filter(growthPhase -> !growthPhase.type().equals(GrowthPhaseType.HARVEST))
                .findFirst()
                .orElseThrow();

        int currentYear = LocalDate.now().getYear();
        return (int) DAYS.between(harvest.startDate().atYear(currentYear), sow.startDate().atYear(currentYear));
    }

    public int lifecycleGroupFromHarvestDate(LocalDate harvestDate) {
        return lifecycle.stream()
                .filter(growthPhase -> growthPhase.type().equals(GrowthPhaseType.HARVEST) &&
                        dateInRange(harvestDate, growthPhase.startDate(), growthPhase.endDate()))
                .map(GrowthPhase::group)
                .findFirst()
                .orElse(0);
    }

    /**
     * Checks if the given {@link LocalDate} is within a {@link GrowthPhase} of the given {@link GrowthPhaseType}
     *
     * @param date The date to check.
     * @param phase The {@link GrowthPhaseType} to match against
     * @return Whether the date is within the given {@link GrowthPhaseType}
     */
    public boolean isDateInPhase(LocalDate date, GrowthPhaseType phase) {
        return lifecycle.stream()
                .filter(growthPhase -> growthPhase.type().equals(phase))
                .anyMatch(growthPhase -> dateInRange(date, growthPhase.startDate(), growthPhase.endDate()));
    }

    /**
     * Checks if the given {@link LocalDate} is within the given {@link MonthDay} range.
     * (regardless of year)
     *
     * @param subject The date to check
     * @param min The start of the date-range
     * @param max The end of the date-range
     * @return Whether the subject is within the range.
     */
    private boolean dateInRange(LocalDate subject, MonthDay min, MonthDay max) {
        return subject.getMonth().compareTo(min.getMonth()) >= 0 &&
                subject.getMonth().compareTo(max.getMonth()) <= 0 &&
                // if the day is less than the minimum day, the minimum month must not be equal
                (subject.getDayOfMonth() >= min.getDayOfMonth() || !subject.getMonth().equals(min.getMonth())) &&
                // if the day is greater than the maximum day, the maximum month must not be equal
                (subject.getDayOfMonth() <= max.getDayOfMonth() || !subject.getMonth().equals(max.getMonth()));
    }
}
