package io.github.wulkanowy.data.repositories.timetable

import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import java.time.LocalDate
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class TimetableRepository @Inject constructor(
    private val local: TimetableLocal,
    private val remote: TimetableRemote,
    private val schedulerHelper: TimetableNotificationSchedulerHelper
) {

    fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean, refreshAdditional: Boolean = false) = networkBoundResource(
        shouldFetch = { (timetable, additional) -> timetable.isEmpty() || (additional.isEmpty() && refreshAdditional) || forceRefresh },
        query = {
            local.getTimetable(semester, start.monday, end.sunday)
                .map { schedulerHelper.scheduleNotifications(it, student); it }
                .combine(local.getTimetableAdditional(semester, start.monday, end.sunday)) { timetable, additional ->
                    timetable to additional
                }
        },
        fetch = { remote.getTimetable(student, semester, start.monday, end.sunday) },
        saveFetchResult = { (oldTimetable, oldAdditional), (newTimetable, newAdditional) ->
            refreshTimetable(student, oldTimetable, newTimetable)
            refreshAdditional(oldAdditional, newAdditional)

        },
        filterResult = { (timetable, additional) ->
            timetable.filter { item ->
                item.date in start..end
            } to additional.filter { item -> item.date in start..end }
        }
    )

    private suspend fun refreshTimetable(student: Student, old: List<Timetable>, new: List<Timetable>) {
        local.deleteTimetable(old.uniqueSubtract(new).also { schedulerHelper.cancelScheduled(it) })
        local.saveTimetable(new.uniqueSubtract(old).also { schedulerHelper.scheduleNotifications(it, student) }.map { item ->
            item.also { new ->
                old.singleOrNull { new.start == it.start }?.let { old ->
                    return@map new.copy(
                        room = if (new.room.isEmpty()) old.room else new.room,
                        teacher = if (new.teacher.isEmpty() && !new.changes && !old.changes) old.teacher else new.teacher
                    )
                }
            }
        })
    }

    private suspend fun refreshAdditional(old: List<TimetableAdditional>, new: List<TimetableAdditional>) {
        local.deleteTimetableAdditional(old.uniqueSubtract(new))
        local.saveTimetableAdditional(new.uniqueSubtract(old))
    }
}
