package com.nisemoe.nise.scheduler

import com.nisemoe.generated.tables.references.SCORES
import com.nisemoe.generated.tables.references.USERS
import com.nisemoe.nise.database.UserService
import com.nisemoe.nise.integrations.DiscordEmbed
import com.nisemoe.nise.integrations.DiscordService
import com.nisemoe.nise.osu.OsuApi
import org.jooq.DSLContext
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Profile
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service
import java.time.OffsetDateTime

@Service
@Profile("import:users")
class ImportUsers(
    private val dslContext: DSLContext,
    private val userService: UserService,
    private val discordService: DiscordService,
    private val osuApi: OsuApi,
) {

    private val logger = LoggerFactory.getLogger(javaClass)

    companion object {

        const val SLEEP_AFTER_API_CALL = 3000L

    }

    @Value("\${WEBHOOK_URL}")
    private lateinit var webhookUrl: String

    @Scheduled(fixedDelay = 20000, initialDelay = 0)
    fun updateUsersScheduler() {
        try {
            this.updateUsers()
        } catch (exception: Exception) {
            val errorEmbed = DiscordEmbed(
                title = "Exception ocurred",
                description = exception.stackTraceToString()
            )
            this.discordService.sendEmbeds(
                this.webhookUrl,
                listOf(errorEmbed)
            )
            this.logger.error(exception.stackTraceToString())
        }
    }

    private fun updateUsers() {
        val pageSize = 50
        var currentPage = 0

        do {
            val offset = currentPage * pageSize

            val userIds = dslContext
                .select(USERS.USER_ID)
                .from(USERS)
                .orderBy(USERS.SYS_LAST_UPDATE.asc())
                .limit(pageSize)
                .offset(offset)
                .fetchInto(Long::class.java)

            // Process the fetched users
            if (userIds.isNotEmpty()) {
                updateUserIds(userIds)
            }

            currentPage++
        } while (userIds.size == pageSize)
    }

    private fun updateUserIds(userIds: List<Long>) {
        val bannedUsersCondition = SCORES.IS_BANNED.eq(true)

        val usersResult = this.osuApi.getUsersBatch(userIds)
        Thread.sleep(SLEEP_AFTER_API_CALL * 2)

        if (usersResult == null) {
            this.logger.error("Failed to get users")
            return
        }

        // Check which ids are missing; if any are missing, we explicitly check if they're banned.
        val missingIds = userIds.filter { it !in usersResult.users.map { it.id } }
        for (missingId in missingIds) {
            dslContext.update(USERS)
                .set(USERS.SYS_LAST_UPDATE, OffsetDateTime.now())
                .where(USERS.USER_ID.eq(missingId))
                .execute()

            // We exclude all users with at least 1 banned score in the last 3 months
            if (dslContext.fetchExists(SCORES, SCORES.USER_ID.eq(missingId).and(bannedUsersCondition))) {
                continue
            }

            val isUserBanned = this.osuApi.checkIfUserBanned(missingId)
            Thread.sleep(SLEEP_AFTER_API_CALL)
            if (isUserBanned == true) {
                this.logger.warn("User $missingId is banned")
                dslContext.update(SCORES)
                    .set(SCORES.IS_BANNED, true)
                    .where(SCORES.USER_ID.eq(missingId))
                    .execute()
            }
        }

        // Update the rest of the users
        for (user in usersResult.users) {
            userService.insertApiUser(user)
        }

        this.logger.info("Updated ${usersResult.users.size} users")
    }

}