package com.github.nullptroma.wallenc.task.runtime

import com.github.nullptroma.wallenc.domain.errors.WallencException
import com.github.nullptroma.wallenc.domain.tasks.TaskLogLevel
import com.github.nullptroma.wallenc.domain.tasks.TaskProgressLabel
import com.github.nullptroma.wallenc.domain.tasks.TaskRunState
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertTrue
import org.junit.Test

@OptIn(ExperimentalCoroutinesApi::class)
class TaskOrchestratorTest {

  private val dispatcher = StandardTestDispatcher()

  @Test
  fun enqueueCompletesTask() = runTest(dispatcher) {
    val orchestrator = TaskOrchestrator(dispatcher)
    val id = orchestrator.enqueue(
      title = "Test",
      dispatcher = dispatcher,
      work = { ctx ->
        ctx.reportProgress(0.5f, TaskProgressLabel.SyncPreparing(1))
      },
    )
    advanceUntilIdle()
    val task = orchestrator.pipelineState.value.tasks.first { it.id == id }
    assertTrue(task.state is TaskRunState.Completed)
  }

  @Test
  fun cancelAllMarksRunningTaskCancelled() = runTest(dispatcher) {
    val orchestrator = TaskOrchestrator(dispatcher)
    val id = orchestrator.enqueue(
      title = "Long",
      dispatcher = dispatcher,
      work = { ctx ->
        ctx.reportProgress(null, null)
        kotlinx.coroutines.delay(60_000)
      },
    )
    advanceTimeBy(1)
    orchestrator.cancelAll()
    advanceUntilIdle()
    val task = orchestrator.pipelineState.value.tasks.first { it.id == id }
    assertTrue(task.state is TaskRunState.Cancelled)
  }

  @Test
  fun cancelMarksTaskCancelled() = runTest(dispatcher) {
    val orchestrator = TaskOrchestrator(dispatcher)
    val id = orchestrator.enqueue(
      title = "Long",
      dispatcher = dispatcher,
      work = { ctx ->
        ctx.reportProgress(null, null)
        kotlinx.coroutines.delay(60_000)
      },
    )
    advanceTimeBy(1)
    orchestrator.cancel(id)
    advanceUntilIdle()
    val task = orchestrator.pipelineState.value.tasks.first { it.id == id }
    assertTrue(task.state is TaskRunState.Cancelled)
  }

  @Test
  fun failRecordsFailedState() = runTest(dispatcher) {
    val orchestrator = TaskOrchestrator(dispatcher)
    val id = orchestrator.enqueue(
      title = "Fail",
      dispatcher = dispatcher,
      work = { ctx ->
        ctx.fail(WallencException.Storage.FileNotFound())
      },
    )
    advanceUntilIdle()
    val task = orchestrator.pipelineState.value.tasks.first { it.id == id }
    val failed = task.state as TaskRunState.Failed
    assertTrue(failed.error is WallencException.Storage.FileNotFound)
  }

  @Test
  fun progressUpdatesRunningState() = runTest(dispatcher) {
    val orchestrator = TaskOrchestrator(dispatcher)
    val id = orchestrator.enqueue(
      title = "Progress",
      dispatcher = dispatcher,
      work = { ctx ->
        ctx.reportProgress(0.25f, TaskProgressLabel.SyncCompleted)
      },
    )
    advanceUntilIdle()
    val task = orchestrator.pipelineState.value.tasks.first { it.id == id }
    assertTrue(task.state is TaskRunState.Completed)
  }

  @Test
  fun logAppendsLine() = runTest(dispatcher) {
    val orchestrator = TaskOrchestrator(dispatcher)
    orchestrator.enqueue(
      title = "Log",
      dispatcher = dispatcher,
      work = { ctx ->
        ctx.log(TaskLogLevel.Info, "hello")
      },
    )
    advanceUntilIdle()
    assertTrue(orchestrator.logLines.value.any { it.message == "hello" })
  }
}
