package dev.inmo.krontab.utils

import com.soywiz.klock.*
import dev.inmo.krontab.KronSchedulerTz
import dev.inmo.krontab.buildSchedule
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.test.runTest
import kotlin.test.*

@ExperimentalCoroutinesApi
@FlowPreview
class StringParseTest {
    @Test
    fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnString() {
        val kronScheduler = buildSchedule("*/1 * * * *")

        val flow = kronScheduler.asFlowWithoutDelays()

        runTest {
            val mustBeCollected = 10
            var collected = 0
            flow.takeWhile {
                collected < mustBeCollected
            }.collect {
                collected++
            }
            assertEquals(mustBeCollected, collected)
        }
    }
    @Test
    fun testThatFlowIsCorrectlyWorkEverySecondWhenMillisIsHalfOfSecondBuiltOnString() {
        val kronScheduler = buildSchedule("*/1 * * * * 500ms")

        val flow = kronScheduler.asFlowWithoutDelays()

        runTest {
            val mustBeCollected = 10
            var collected = 0
            flow.takeWhile {
                collected < mustBeCollected
            }.collect {
                collected++
            }
            assertEquals(mustBeCollected, collected)
        }
    }

    @Test
    fun testThatFlowIsCorrectlyWorkEverySecondWithMuchOfEmittersBuiltOnString() {
        val kronScheduler = buildSchedule("*/1 * * * *")

        val flow = kronScheduler.asFlowWithoutDelays()

        runTest {
            val testsCount = 10
            val failJob = createFailJob((testsCount * 2) * 1000L)
            val mustBeCollected = 10
            val answers = (0 until testsCount).map { _ ->
                async {
                    var collected = 0
                    flow.takeWhile {
                        collected < mustBeCollected
                    }.collect {
                        collected++
                    }
                    collected
                }
            }.awaitAll()

            failJob.cancel()

            answers.forEach {
                assertEquals(mustBeCollected, it)
            }
        }
    }
    @Test
    fun testThatFlowIsCorrectlyWorkEverySeveralSecondsRangeBuiltOnString() {
        val rangesEnds = listOf(0 to 5, 30 to 35)
        val kronScheduler = buildSchedule("${rangesEnds.joinToString(",") { "${it.first}-${it.second}" }} * * * *")

        val flow = kronScheduler.asFlowWithoutDelays()

        runTest {
            val ranges = rangesEnds.map { it.first .. it.second }.flatten().distinct().toMutableList()
            val expectedCollects = ranges.size
            var collected = 0

            flow.takeWhile { ranges.isNotEmpty() }.collect {
                ranges.remove(it.seconds)
                collected++
                assertTrue(
                    collected <= expectedCollects,
                    "Expected value should be less than $expectedCollects, but was $collected. Ranges state: $ranges"
                )
            }
            assertEquals(expectedCollects, collected)
        }
    }
    @Test
    fun testNextIsCorrectlyWorkEverySeveralMillisecondsRangeBuiltOnString() {
        val rangesEnds = listOf(0, 200, 500, 750)
        val kronScheduler = buildSchedule("* * * * * ${rangesEnds.joinToString(",") { "$it" }}ms")

        runTest {
            val ranges = rangesEnds.toMutableList()
            val expectedCollects = ranges.size
            var collected = 0

            var currentTime = DateTime.now()
            while (ranges.isNotEmpty()) {
                val nextTrigger = kronScheduler.next(currentTime) ?: error("Strangely unable to get next time")

                ranges.remove(nextTrigger.milliseconds)
                collected++

                currentTime = nextTrigger + 1.milliseconds
            }
            assertEquals(expectedCollects, collected)
        }
    }
    @Test
    fun testThatTimezoneCorrectlyDeserialized() {
        val now = DateTime.now().copy(milliseconds = 0).local

        runTest {
            for (i in 0 .. 1339) {
                val expectedInCurrentOffset = now.toOffset(TimezoneOffset(i.minutes)) + 1.hours
                val kronScheduler = buildSchedule(
                    "${expectedInCurrentOffset.seconds} ${expectedInCurrentOffset.minutes} ${expectedInCurrentOffset.hours} * * ${i}o"
                ) as KronSchedulerTz
                val next = kronScheduler.next(now)
                assertEquals(expectedInCurrentOffset.toOffset(now.offset), next)
            }
        }
    }
}