package ru.forbion.f7cloud.feature.calendar

import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import java.time.LocalDate

/**
 * Разворот повторений (P1): [CalendarRecurrence.occursOnDay] / [occurrenceStartsInRange].
 */
class CalendarRecurrenceTest {

    private fun rule(s: String) = RecurrenceRule.parse(s)!!
    private fun d(s: String) = LocalDate.parse(s)

    private fun occurs(rrule: String, start: String, day: String, span: Long = 1, allDay: Boolean = false) =
        CalendarRecurrence.occursOnDay(rule(rrule), d(start), d(day), span, allDay)

    @Test fun daily_hits_every_day() {
        assertTrue(occurs("FREQ=DAILY", "2026-07-01", "2026-07-01"))
        assertTrue(occurs("FREQ=DAILY", "2026-07-01", "2026-07-15"))
        assertFalse("до старта — нет", occurs("FREQ=DAILY", "2026-07-10", "2026-07-05"))
    }

    @Test fun daily_interval_2_skips_odd_offsets() {
        assertTrue(occurs("FREQ=DAILY;INTERVAL=2", "2026-07-01", "2026-07-03"))
        assertFalse(occurs("FREQ=DAILY;INTERVAL=2", "2026-07-01", "2026-07-02"))
    }

    @Test fun weekly_same_weekday() {
        // 2026-07-01 — среда; +неделя = 08, 15…
        assertTrue(occurs("FREQ=WEEKLY", "2026-07-01", "2026-07-08"))
        assertTrue(occurs("FREQ=WEEKLY", "2026-07-01", "2026-07-29"))
        assertFalse(occurs("FREQ=WEEKLY", "2026-07-01", "2026-07-09"))
    }

    @Test fun weekly_byday_mon_wed_fri() {
        // старт среда 2026-07-01; BYDAY=MO,WE,FR
        val r = "FREQ=WEEKLY;BYDAY=MO,WE,FR"
        assertTrue("среда старта", occurs(r, "2026-07-01", "2026-07-01"))
        assertTrue("пятница той же недели", occurs(r, "2026-07-01", "2026-07-03"))
        assertTrue("понедельник след. недели", occurs(r, "2026-07-01", "2026-07-06"))
        assertFalse("вторник — нет", occurs(r, "2026-07-01", "2026-07-07"))
        assertFalse("понедельник ДО старта той же недели — нет", occurs(r, "2026-07-01", "2026-06-29"))
    }

    @Test fun weekly_byday_interval_2_skips_middle_week() {
        val r = "FREQ=WEEKLY;INTERVAL=2;BYDAY=MO"
        // старт среда 01.07; понедельники: блок0 нед. 29.06 (до старта, нет), блок2 нед. 13.07 → 13
        assertTrue(occurs(r, "2026-07-01", "2026-07-13"))
        assertFalse("промежуточная неделя 06.07 пропущена", occurs(r, "2026-07-01", "2026-07-06"))
    }

    @Test fun monthly_same_day_of_month() {
        assertTrue(occurs("FREQ=MONTHLY", "2026-01-15", "2026-04-15"))
        assertFalse(occurs("FREQ=MONTHLY", "2026-01-15", "2026-04-16"))
    }

    @Test fun yearly() {
        assertTrue(occurs("FREQ=YEARLY", "2024-02-29", "2028-02-29"))
        assertTrue(occurs("FREQ=YEARLY", "2026-03-10", "2030-03-10"))
        assertFalse(occurs("FREQ=YEARLY", "2026-03-10", "2027-03-11"))
    }

    @Test fun count_limits_occurrences() {
        // COUNT=3 → 01, 02, 03; 04 уже нет
        assertTrue(occurs("FREQ=DAILY;COUNT=3", "2026-07-01", "2026-07-03"))
        assertFalse(occurs("FREQ=DAILY;COUNT=3", "2026-07-01", "2026-07-04"))
    }

    @Test fun until_bounds_occurrences() {
        assertTrue(occurs("FREQ=DAILY;UNTIL=20260705T235959Z", "2026-07-01", "2026-07-05"))
        assertFalse(occurs("FREQ=DAILY;UNTIL=20260705T235959Z", "2026-07-01", "2026-07-06"))
    }

    @Test fun allday_multiday_occurrence_covers_span() {
        // 2-дневное all-day (span=2), еженедельно: оккуренция 08.07 покрывает 08 и 09
        assertTrue(occurs("FREQ=WEEKLY", "2026-07-01", "2026-07-09", span = 2, allDay = true))
        assertFalse(occurs("FREQ=WEEKLY", "2026-07-01", "2026-07-10", span = 2, allDay = true))
    }

    @Test fun monthly_nth_weekday_first_monday() {
        // первый понедельник: июль'26 = 06, авг = 03, сен = 07
        val r = "FREQ=MONTHLY;BYDAY=1MO"
        assertTrue(occurs(r, "2026-07-06", "2026-07-06"))
        assertTrue(occurs(r, "2026-07-06", "2026-08-03"))
        assertTrue(occurs(r, "2026-07-06", "2026-09-07"))
        assertFalse("второй понедельник — нет", occurs(r, "2026-07-06", "2026-07-13"))
        assertFalse("число месяца, не день недели", occurs(r, "2026-07-06", "2026-08-06"))
    }

    @Test fun monthly_last_friday() {
        // последняя пятница: июль'26 = 31, авг = 28
        val r = "FREQ=MONTHLY;BYDAY=-1FR"
        assertTrue(occurs(r, "2026-07-31", "2026-07-31"))
        assertTrue(occurs(r, "2026-07-31", "2026-08-28"))
        assertFalse(occurs(r, "2026-07-31", "2026-08-21"))
    }

    @Test fun byday_ordinal_round_trips_through_rrule() {
        // раньше toRrule терял ординал (1MO → MO); теперь сохраняет.
        assertEquals("FREQ=MONTHLY;BYDAY=1MO", rule("FREQ=MONTHLY;BYDAY=1MO").toRrule())
        assertEquals("FREQ=MONTHLY;BYDAY=-1FR", rule("FREQ=MONTHLY;BYDAY=-1FR").toRrule())
        assertEquals(1, rule("FREQ=MONTHLY;BYDAY=1MO").monthlyOrdinal)
    }

    @Test fun range_listing() {
        val starts = CalendarRecurrence.occurrenceStartsInRange(
            rule = rule("FREQ=WEEKLY"),
            start = d("2026-07-01"),
            rangeStart = d("2026-07-05"),
            rangeEnd = d("2026-07-25"),
        )
        assertEquals(listOf(d("2026-07-08"), d("2026-07-15"), d("2026-07-22")), starts)
    }
}
