package ch.soobr.common.domain;

import java.time.*;
import java.time.temporal.ChronoField;
import java.util.TimeZone;

/**
 * This is a helper class for all kinds of TimeZone issues
 */
public  class TimeZoneHelper {
    // TimeZone Operations work with GMT if the given TimeZone cannot be parsed

    public static ZonedDateTime setToZone(ZonedDateTime date, TimeZone timeZone){
        if(timeZone == null){
            return date;
        }

        return date.withZoneSameInstant(timeZone.toZoneId());
    }

    /**
     * Simple check day of week but in the current zone of the economic entity
     */
    public static DayOfWeek getDayOfWeekInTheZone(ZonedDateTime date, TimeZone timeZone) {
        return setToZone(date, timeZone).getDayOfWeek();
    }

    public static Integer getMonthInTheZone(ZonedDateTime date, TimeZone timeZone) {
        return setToZone(date, timeZone).getMonthValue();
    }

    public static ZonedDateTime setToUtc(ZonedDateTime date){
        return date.withZoneSameInstant(ZoneOffset.UTC);
    }

    /**
     * This changes only the timezone without converting the time
     */
    private static ZonedDateTime setToZoneWithoutTimeChange(ZonedDateTime date, TimeZone timeZone) {
        if(timeZone == null){
            return date;
        }

        return date.withZoneSameLocal(timeZone.toZoneId());
    }

    /**
     * This is the function to convert the client zone (browser) to the economic entity zone without changing the time and after
     * that covert the time to UTC to store it correct in the database.
     * @param date -> client date with timezone. Attention check correct deserializer
     * @param timeZone economic entity time zone
     * @return UTC date
     */
    public static ZonedDateTime setClientZoneToEconomicEntityZoneFinalToUTC(ZonedDateTime date, TimeZone timeZone) {
        ZonedDateTime zonedDateTime = setToZoneWithoutTimeChange(date, timeZone);
        return setToUtc(zonedDateTime);
    }

    public static ZonedDateTime setToZonedMidnight(ZonedDateTime date, TimeZone timeZone){
        ZoneId zoneId = timeZone == null ? ZoneOffset.UTC : timeZone.toZoneId();

        ZonedDateTime zonedDateTime = date.withZoneSameInstant(zoneId);
        zonedDateTime = zonedDateTime.withHour(0).withMinute(0).withSecond(0).with(ChronoField.MILLI_OF_SECOND, 0);

        return zonedDateTime.withZoneSameInstant(ZoneOffset.UTC);
    }

    /**
     * This is the function to set a LocalTime on a ZonedDateTime
     * by setting it to midnight and adding the time-values
     *
     * @param date DateTime to set new time
     * @param timeZone economic entity time zone
     * @param time Time to set on DateTime
     * @return UTC date
     */
    public static ZonedDateTime setLocalTimeOnZonedDate(ZonedDateTime date, TimeZone timeZone, LocalTime time) {
        ZonedDateTime value = setToZonedMidnight(date, timeZone);

        value = value.plusHours(time.getHour());
        value = value.plusMinutes(time.getMinute());
        value = value.plusSeconds(time.getSecond());
        value = value.plusNanos(time.getNano());

        return value;
    }

    public static Instant getInstantFromZonedDateTime(ZonedDateTime zonedDateTime) {
        if (zonedDateTime != null) {
            return zonedDateTime.toInstant();
        } else {
            return null;
        }
    }

    public static ZonedDateTime getZonedDateTimeFormInstant(Instant instant) {
        if (instant == null) {
            return null;
        }
        return instant.atZone(ZoneOffset.UTC);
    }

    public static boolean isMidnight(TimeZone economicEntityTimeZone, ZonedDateTime now) {
        // this is only for testing -> date time zone is never null!
        if (economicEntityTimeZone == null) {
            return true;
        }
        ZonedDateTime midnightAtZone = setToZonedMidnight(now, economicEntityTimeZone);
        return now.isAfter(midnightAtZone) && now.isBefore(midnightAtZone.plusHours(1));
    }

    public static ZonedDateTime setToZonedNight(ZonedDateTime date, TimeZone timeZone) {
        ZoneId zoneId = timeZone == null ? ZoneOffset.UTC : timeZone.toZoneId();

        ZonedDateTime zonedDateTime = date.withZoneSameInstant(zoneId);
        zonedDateTime = zonedDateTime.withHour(23).withMinute(59).withSecond(59).with(ChronoField.MILLI_OF_SECOND, 0);

        return zonedDateTime.withZoneSameInstant(ZoneOffset.UTC);
    }
}
