
package dev.sonpx.loyalty.mcp.model.valueobject;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Optional;

public class TimeValue extends BaseValue<LocalTime> {

    public static ZoneId SYSTEM_ZONE = ZoneId.systemDefault();

    private static final TimeValue NULL = new TimeValue(null);
    private static final int DEFAULT_SECONDS = 0;
    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");

    private TimeValue(LocalTime value) {
        super(value);
    }

    /**
     * Create a TimeValue instance from an object.
     *
     * @param object an object that could be a LocalTime, LocalDateTime, LocalDate, or String.
     * @return a TimeValue instance representing the specified value, or an NULL instance if not applicable.
     */
    public static TimeValue of(Object object) {
        if (object instanceof LocalTime localTime) {
            return new TimeValue(localTime.withSecond(DEFAULT_SECONDS));
        } else if (object instanceof LocalDateTime localDateTime) {
            return new TimeValue(localDateTime.toLocalTime().withSecond(DEFAULT_SECONDS));
        } else if (object instanceof LocalDate localDate) {
            return new TimeValue(localDate.atStartOfDay().toLocalTime().withSecond(DEFAULT_SECONDS));
        } else if (object instanceof String string) {
            return parseString(string).orElse(NULL);
        }
        return NULL;
    }

    /**
     * Parse a time string into an Optional TimeValue using a specific formatter.
     *
     * @param string the time string to parse.
     * @return an Optional containing a TimeValue if parsing is successful, otherwise an empty Optional.
     */
    private static Optional<TimeValue> parseString(String string) {
        try {
            LocalTime time = LocalTime.parse(string, TIME_FORMATTER);
            return Optional.of(new TimeValue(time.withSecond(DEFAULT_SECONDS)));
        } catch (DateTimeParseException e) {
            try {
                ZonedDateTime utcDateTime = ZonedDateTime.parse(string);
                ZonedDateTime serverDateTime = utcDateTime.withZoneSameInstant(SYSTEM_ZONE);
                return Optional.of(new TimeValue(serverDateTime.toLocalTime().withSecond(DEFAULT_SECONDS)));
            } catch (DateTimeParseException ignored) {
                return Optional.empty();
            }
        }
    }

    /**
     * Check if the current TimeValue is on or before another TimeValue.
     *
     * @param compareValue the TimeValue to compare with.
     * @return true if the current TimeValue is on or before compareValue, otherwise false.
     */
    public boolean isOnOrBefore(TimeValue compareValue) {
        if (isNull()) {
            return false;
        }
        return this.value.isBefore(compareValue.value) || this.value.equals(compareValue.value);
    }

    /**
     * Check if the current TimeValue is on or after another TimeValue.
     *
     * @param compareValue the TimeValue to compare with.
     * @return true if the current TimeValue is on or after compareValue, otherwise false.
     */
    public boolean isOnOrAfter(TimeValue compareValue) {
        if (isNull()) {
            return false;
        }
        return this.value.isAfter(compareValue.value) || this.value.equals(compareValue.value);
    }

    /**
     * Check if the current TimeValue is between two other TimeValues.
     *
     * @param start the starting TimeValue (inclusive).
     * @param end the ending TimeValue (inclusive).
     * @return true if the current TimeValue is between start and end, otherwise false.
     */
    public boolean isBetween(TimeValue start, TimeValue end) {
        if (isNull()) {
            return false;
        } else if (start.isNull()) {
            return this.isOnOrBefore(end);
        } else if (end.isNull()) {
            return this.isOnOrAfter(start);
        }
        return this.isOnOrAfter(start) && this.isOnOrBefore(end);
    }
}
