package ee.tuleva.onboarding.deadline;

import static java.time.DayOfWeek.SATURDAY;
import static java.time.DayOfWeek.SUNDAY;
import static java.time.temporal.TemporalAdjusters.lastDayOfMonth;

import ee.tuleva.onboarding.mandate.application.ApplicationType;
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class MandateDeadlines {

  private final Clock estonianClock;
  private final PublicHolidays publicHolidays;
  private final Instant applicationDate;

  public Instant getPeriodEnding() {
    return periodEnding().toInstant();
  }

  private ZonedDateTime periodEnding() {
    ZonedDateTime march31ThisYear = now().withMonth(3).with(lastDayOfMonth()).with(LocalTime.MAX);
    ZonedDateTime july31ThisYear = now().withMonth(7).with(lastDayOfMonth()).with(LocalTime.MAX);
    ZonedDateTime november30ThisYear =
        now().withMonth(11).with(lastDayOfMonth()).with(LocalTime.MAX);
    ZonedDateTime march31NextYear =
        now().plusYears(1).withMonth(3).with(lastDayOfMonth()).with(LocalTime.MAX);

    return Stream.of(march31ThisYear, july31ThisYear, november30ThisYear, march31NextYear)
        .filter(deadline -> !deadline.isBefore(applicationDate.atZone(estonianClock.getZone())))
        .findFirst()
        .get();
  }

  public Instant getTransferMandateCancellationDeadline() {
    return getPeriodEnding();
  }

  public LocalDate getTransferMandateFulfillmentDate() {
    return nextWorkingDay(periodEnding().plusMonths(1).with(lastDayOfMonth()).toLocalDate());
  }

  public Instant getEarlyWithdrawalCancellationDeadline() {
    return earlyWithdrawalCancellationDeadline().toInstant();
  }

  private ZonedDateTime earlyWithdrawalCancellationDeadline() {
    return periodEnding().plusMonths(4).with(lastDayOfMonth());
  }

  public LocalDate getEarlyWithdrawalFulfillmentDate() {
    return nextWorkingDay(
        earlyWithdrawalCancellationDeadline().plusMonths(1).with(lastDayOfMonth()).toLocalDate());
  }

  public Instant getWithdrawalCancellationDeadline() {
    return withdrawalCancellationDeadline().toInstant();
  }

  private ZonedDateTime withdrawalCancellationDeadline() {
    return applicationDate
        .atZone(estonianClock.getZone())
        .with(lastDayOfMonth())
        .with(LocalTime.MAX);
  }

  public LocalDate getWithdrawalFulfillmentDate() {
    return nextWorkingDay(withdrawalCancellationDeadline().plusDays(15).toLocalDate());
  }

  public LocalDate getFulfillmentDate(ApplicationType applicationType) {
    return switch (applicationType) {
      case TRANSFER -> getTransferMandateFulfillmentDate();
      case WITHDRAWAL -> getWithdrawalFulfillmentDate();
      case EARLY_WITHDRAWAL -> getEarlyWithdrawalFulfillmentDate();
      default -> throw new IllegalArgumentException("Unknown application type: " + applicationType);
    };
  }

  public Instant getCancellationDeadline(ApplicationType applicationType) {
    return switch (applicationType) {
      case TRANSFER -> getTransferMandateCancellationDeadline();
      case WITHDRAWAL -> getWithdrawalCancellationDeadline();
      case EARLY_WITHDRAWAL -> getEarlyWithdrawalCancellationDeadline();
      default -> throw new IllegalArgumentException("Unknown application type: " + applicationType);
    };
  }

  private LocalDate nextWorkingDay(LocalDate to) {
    LocalDate date = to.plusDays(1);
    while (!isWorkingDay(date)) {
      date = date.plusDays(1);
    }
    return date;
  }

  private boolean isWorkingDay(LocalDate date) {
    return !(date.getDayOfWeek() == SATURDAY
        || date.getDayOfWeek() == SUNDAY
        || publicHolidays.isPublicHoliday(date));
  }

  private ZonedDateTime now() {
    return ZonedDateTime.now(estonianClock);
  }
}

@@ -354,24 +354,73 @@
 .LocalTime;%0A
+import java.time.Month;%0Aimport java.time.ZoneId;%0A
 import java.
@@ -829,174 +829,410 @@
 Zone
-dDateTime march31ThisYear = now().withMonth(3).with(lastDayOfMonth()).with(LocalTime.MAX);%0A    ZonedDateTime july31ThisYear = now().withMonth(7).with(lastDayOfMonth()
+Id timeZone = estonianClock.getZone();%0A    ZonedDateTime zonedApplicationDate = applicationDate.atZone(timeZone);%0A    int applicationYear = zonedApplicationDate.getYear();%0A%0A    ZonedDateTime march31 =%0A        LocalDate.of(applicationYear, Month.MARCH, 31).atStartOfDay(timeZone).with(LocalTime.MAX);%0A    ZonedDateTime july31 =%0A        LocalDate.of(applicationYear, Month.JULY, 31).atStartOfDay(timeZone
 ).wi
@@ -1278,24 +1278,16 @@
 vember30
-ThisYear
  =%0A     
@@ -1293,189 +1293,183 @@
     
-now().withMonth(11).with(lastDayOfMonth()).with(LocalTime.MAX);%0A    ZonedDateTime march31NextYear =%0A        now().plusYears(1).withMonth(3).with(lastDayOfMonth()).with(LocalTime.MAX
+LocalDate.of(applicationYear, Month.NOVEMBER, 30)%0A            .atStartOfDay(timeZone)%0A            .with(LocalTime.MAX);%0A    ZonedDateTime march31NextYear = march31.plusYears(1
 );%0A%0A
@@ -1500,32 +1500,16 @@
 ch31
-ThisYear, july31ThisYear
+, july31
 , no
@@ -1520,16 +1520,8 @@
 er30
-ThisYear
 , ma
@@ -1582,17 +1582,22 @@
 sBefore(
-a
+zonedA
 pplicati
@@ -1606,40 +1606,8 @@
 Date
-.atZone(estonianClock.getZone())
 ))%0A 

Fix early withdrawal fulfillment date