package io.delphiplatform.api.util;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.Comparator;
import java.util.stream.Stream;

import javax.annotation.Nullable;

import io.delphiplatform.api.util.service.CurrentDateService;
import lombok.NonNull;

@Component
public class DateUtils {

    private static final String dateFormat = "yyyy-MM-dd";
    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);

    private static CurrentDateService currentDateService;

    public DateUtils(CurrentDateService currentDateService) {
        if (DateUtils.currentDateService != null) {
            throw new IllegalStateException("DateUtils instance already exists");
        }
        DateUtils.currentDateService = currentDateService;
    }

    public static LocalDate getCurrentDate() {
        return currentDateService.getCurrentDate();
    }

    public static LocalDateTime getCurrentDateTime() {
        return currentDateService.getCurrentDateTime();
    }

    public static String formatWeek(LocalDate weekStart) {
        if (weekStart == null) {
            return null;
        }
        return weekStart.format(DateTimeFormatter.ISO_DATE) + "/"
            + weekStart.plusDays(6).format(DateTimeFormatter.ISO_DATE);
    }

    /**
     * Formats a date object as a string
     *
     * @param date A date object
     * @return the date as a formatted string
     */
    public static String format(@NonNull LocalDate date) {
        return date.format(formatter);
    }

    /**
     * Parses a string to create a date object
     *
     * @param date A date string
     * @return the parsed date object
     */
    public static LocalDate parse(@NonNull String date) {
        return LocalDate.parse(date, formatter);
    }

    /**
     * Parses a string to create a date object, returns null for null or empty string
     *
     * @param date A date string (nullable)
     * @return the parsed date object
     */
    @Nullable
    public static LocalDate parseSafe(@Nullable String date) {
        return StringUtils.isNotEmpty(date) ? LocalDate.parse(date, formatter) : null;
    }

    /**
     * Checks if start date is less than or equal to end date
     *
     * @param start first date in range
     * @param end   last date in range
     * @return true if {@code start} occurs before {@code end}
     */
    public static boolean isValidRange(@NonNull LocalDate start, @NonNull LocalDate end) {
        return start.compareTo(end) <= 0;
    }

    /**
     * Checks if start date occurs before end date
     *
     * @param start first date in range
     * @param end   last date in range
     * @return true if {@code start} occurs before {@code end}
     */
    public static boolean isValidRange(@NonNull String start, @NonNull String end) {
        return isValidRange(parse(start), parse(end));
    }

    public static boolean betweenDates(LocalDate date, LocalDate startDate, LocalDate endDate) {
        return date.isAfter(startDate) && date.isBefore(endDate);
    }

    public static LocalDate getMinDate(Stream<LocalDate> stream) {
        return getDate(stream, false);
    }

    public static LocalDate getMaxDate(Stream<LocalDate> stream) {
        return getDate(stream, true);
    }

    private static LocalDate getDate(Stream<LocalDate> stream, boolean max) {
        return stream
            .max(max ? Comparator.naturalOrder() : Comparator.reverseOrder())
            .orElseThrow();
    }

    public static LocalDate toLocalDateSafe(LocalDateTime dateTime) {
        return dateTime == null ? null : dateTime.toLocalDate();
    }

    public static LocalDate getChartStartDate(LocalDate date) {
        return (date.getDayOfWeek().equals(DayOfWeek.THURSDAY))
            ? date
            : date.with(TemporalAdjusters.previous(DayOfWeek.THURSDAY));
    }
}

