package ru.fincode.tsudesk.feature.schedule.data

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import ru.fincode.tsudesk.core.common.model.DataResult
import ru.fincode.tsudesk.core.network.model.NetworkResult
import ru.fincode.tsudesk.core.network.model.map
import ru.fincode.tsudesk.feature.schedule.data.datasource.ScheduleLocalDataSource
import ru.fincode.tsudesk.feature.schedule.data.datasource.ScheduleRemoteDataSource
import ru.fincode.tsudesk.feature.schedule.data.remote.ScheduleNetworkMapper
import ru.fincode.tsudesk.feature.schedule.domain.model.ScheduleEntity
import ru.fincode.tsudesk.feature.schedule.domain.model.ScheduleType
import ru.fincode.tsudesk.feature.schedule.domain.repository.ScheduleRepository
import javax.inject.Inject

class ScheduleRepositoryImpl @Inject constructor(
    private val remote: ScheduleRemoteDataSource,
    private val local: ScheduleLocalDataSource,
    private val mapper: ScheduleNetworkMapper
) : ScheduleRepository {

    override fun observeSchedule(type: ScheduleType): Flow<DataResult<ScheduleEntity>> = flow {
        val scheduleKey = type.toCacheKey()
        val cached = local.observeSchedule(scheduleKey).first()

        if (cached != null) {
            emit(DataResult.Data(cached, refreshedFromNetwork = false))
            if ((System.currentTimeMillis() - cached.timestamp) < CACHE_TTL_MS) return@flow
        } else {
            emit(DataResult.Loading)
        }

        val networkResult = loadScheduleDto(type).map(mapper::invoke)
        if (networkResult is NetworkResult.Error) {
            if (cached != null) return@flow
            emit(DataResult.Error(networkResult.error.toAppError()))
            return@flow
        }

        val networkSchedule = (networkResult as NetworkResult.Success).data
        local.saveSchedule(scheduleKey, networkSchedule)

        val updated = local.loadSchedule(scheduleKey)
        if (updated != null && cached?.timestamp != updated.timestamp) {
            emit(DataResult.Data(updated, refreshedFromNetwork = true))
        }
    }

    private suspend fun loadScheduleDto(
        type: ScheduleType
    ): NetworkResult<ru.fincode.tsudesk.feature.schedule.data.remote.dto.ScheduleDto> =
        when (type) {
            is ScheduleType.Group -> remote.loadScheduleByGroup(type.number)
            is ScheduleType.Teacher -> remote.loadScheduleByTeacher(type.name)
        }

    private companion object {
        private const val CACHE_TTL_MS: Long = 3L * 24L * 60L * 60L * 1000L // 3 дня
    }
}