package com.nisemoe.nise.scheduler

import com.nisemoe.generated.tables.records.ScoresRecord
import com.nisemoe.generated.tables.references.BEATMAPS
import com.nisemoe.generated.tables.references.SCORES
import com.nisemoe.generated.tables.references.SCORES_SIMILARITY
import com.nisemoe.nise.Format
import com.nisemoe.nise.database.ScoreService
import com.nisemoe.nise.integrations.DiscordEmbed
import com.nisemoe.nise.integrations.DiscordService
import com.nisemoe.nise.osu.Mod
import org.jooq.DSLContext
import org.jooq.Record
import org.jooq.impl.DSL
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.LocalDateTime
import java.time.OffsetDateTime
import kotlin.math.roundToInt

@Profile("discord")
@Service
class SendScoresToDiscord(
    private val dslContext: DSLContext,
    private val scoreService: ScoreService,
    private val discordService: DiscordService
) {

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

    private val logger = LoggerFactory.getLogger(javaClass)

    // 2024-03-03 00:00:00
    private val minCutoff = OffsetDateTime.of(
        2024, 3, 5, 0, 0, 0, 0, OffsetDateTime.now().offset
    )

    @Scheduled(fixedDelay = 1200000, initialDelay = 0)
    fun sendScoresToDiscord() {
        dslContext.selectFrom(SCORES)
            .where(SCORES.SENT_DISCORD_NOTIFICATION.isFalse)
            .or(SCORES.SENT_DISCORD_NOTIFICATION.isNull)
            .and(SCORES.ADDED_AT.greaterOrEqual(minCutoff))
            .and(SCORES.ADJUSTED_UR.isNotNull)
            .and(SCORES.ADJUSTED_UR.lessOrEqual(25.0))
            .and(SCORES.IS_BANNED.isFalse)
            .limit(5)
            .stream().use { stream ->
                stream.forEach { score -> processSuspiciousScore(score) }
            }

        dslContext.select(DSL.asterisk())
            .from(SCORES_SIMILARITY)

            .join(ScoreService.osuScoreAlias1).on(ScoreService.osuScoreAlias1.REPLAY_ID.eq(SCORES_SIMILARITY.REPLAY_ID_1))
            .join(ScoreService.osuUserAlias1).on(ScoreService.osuScoreAlias1.USER_ID.eq(ScoreService.osuUserAlias1.USER_ID))

            .leftJoin(ScoreService.osuScoreAlias2).on(ScoreService.osuScoreAlias2.REPLAY_ID.eq(SCORES_SIMILARITY.REPLAY_ID_2))
            .leftJoin(ScoreService.osuUserAlias2).on(ScoreService.osuScoreAlias2.USER_ID.eq(ScoreService.osuUserAlias2.USER_ID))

            .join(BEATMAPS).on(BEATMAPS.BEATMAP_ID.eq(ScoreService.osuScoreAlias1.BEATMAP_ID))
            .where(SCORES_SIMILARITY.SENT_DISCORD_NOTIFICATION.isFalse)
            .and(ScoreService.osuScoreAlias1.IS_BANNED.eq(false))
            .and(ScoreService.osuScoreAlias2.IS_BANNED.eq(false))
            .or(SCORES_SIMILARITY.SENT_DISCORD_NOTIFICATION.isNull)
            .limit(5)
            .stream().use { stream ->
                stream.forEach { score -> processStolenReplay(score) }
            }
    }

    fun processStolenReplay(scoresSimilarityRecord: Record) {
        val olderReplayDate: LocalDateTime?
        val olderReplayPP: Double?
        val olderReplayId: Long?
        val olderReplayUsername: String?
        val olderReplayUserId: Long?
        val olderReplayMods: Int?

        val newerReplayDate: LocalDateTime?
        val newerReplayPP: Double?
        val newerReplayId: Long?
        val newerReplayUsername: String?
        val newerReplayUserId: Long?
        val newerReplayMods: Int?

        val replay1Date = scoresSimilarityRecord.get(ScoreService.osuScoreAlias1.DATE)
        val replay2Date = scoresSimilarityRecord.get(ScoreService.osuScoreAlias2.DATE)

        if(replay1Date == null || replay2Date == null) {
            logger.error("Failed to fetch replay dates for scores similarity record with id = ${scoresSimilarityRecord.get(SCORES_SIMILARITY.ID)}")
            return
        }

        if (replay1Date.isBefore(replay2Date)) {
            olderReplayId = scoresSimilarityRecord.get(SCORES_SIMILARITY.REPLAY_ID_1)
            olderReplayUsername = scoresSimilarityRecord.get(ScoreService.osuUserAlias1.USERNAME)
            olderReplayUserId = scoresSimilarityRecord.get(ScoreService.osuUserAlias1.USER_ID)
            olderReplayPP = scoresSimilarityRecord.get(ScoreService.osuScoreAlias1.PP)
            olderReplayDate = replay1Date
            olderReplayMods = scoresSimilarityRecord.get(ScoreService.osuScoreAlias1.MODS)

            newerReplayId = scoresSimilarityRecord.get(SCORES_SIMILARITY.REPLAY_ID_2)
            newerReplayUsername = scoresSimilarityRecord.get(ScoreService.osuUserAlias2.USERNAME)
            newerReplayUserId = scoresSimilarityRecord.get(ScoreService.osuUserAlias2.USER_ID)
            newerReplayPP = scoresSimilarityRecord.get(ScoreService.osuScoreAlias2.PP)
            newerReplayDate = replay2Date
            newerReplayMods = scoresSimilarityRecord.get(ScoreService.osuScoreAlias2.MODS)
        } else {
            olderReplayId = scoresSimilarityRecord.get(SCORES_SIMILARITY.REPLAY_ID_2)
            olderReplayUsername = scoresSimilarityRecord.get(ScoreService.osuUserAlias2.USERNAME)
            olderReplayUserId = scoresSimilarityRecord.get(ScoreService.osuUserAlias2.USER_ID)
            olderReplayPP = scoresSimilarityRecord.get(ScoreService.osuScoreAlias2.PP)
            olderReplayDate = replay2Date
            olderReplayMods = scoresSimilarityRecord.get(ScoreService.osuScoreAlias2.MODS)

            newerReplayId = scoresSimilarityRecord.get(SCORES_SIMILARITY.REPLAY_ID_1)
            newerReplayUsername = scoresSimilarityRecord.get(ScoreService.osuUserAlias1.USERNAME)
            newerReplayUserId = scoresSimilarityRecord.get(ScoreService.osuUserAlias1.USER_ID)
            newerReplayPP = scoresSimilarityRecord.get(ScoreService.osuScoreAlias1.PP)
            newerReplayDate = replay1Date
            newerReplayMods = scoresSimilarityRecord.get(ScoreService.osuScoreAlias1.MODS)
        }

        val statisticsEmbed = DiscordEmbed(
            title = "Possible stolen replay",
            description = """
[Replay 1 Link](https://nise.moe/s/$olderReplayId) | [osu!web](https://osu.ppy.sh/scores/osu/$olderReplayId)
   - Played by: [$olderReplayUsername](https://osu.ppy.sh/users/$olderReplayUserId)
   - Played at: ${Format.formatLocalDateTime(olderReplayDate)}
   - PP: ${olderReplayPP?.roundToInt()}${if (olderReplayMods != null) " (${Mod.print(olderReplayMods)})" else ""}

[Replay 2 Link](https://nise.moe/s/$newerReplayId) | [osu!web](https://osu.ppy.sh/scores/osu/$newerReplayId)
   - Played by: [$newerReplayUsername](https://osu.ppy.sh/users/$newerReplayUserId)
   - Played at: ${Format.formatLocalDateTime(newerReplayDate)}
   - PP: ${newerReplayPP?.roundToInt()}${if (newerReplayMods != null) " (${Mod.print(newerReplayMods)})" else ""}
""".trimIndent(),
            url = "https://nise.moe/p/$olderReplayId/$newerReplayId"
        )
        statisticsEmbed.color = 0xB26ECC
        statisticsEmbed.setImageUrl("https://assets.ppy.sh/beatmaps/${scoresSimilarityRecord.get(BEATMAPS.BEATMAPSET_ID)}/covers/cover.jpg")
        statisticsEmbed.setAuthor(
            name = "/nise.moe/'s discord feed", url = "https://nise.moe/", iconUrl = "https://nise.moe/assets/keisatsu-chan.png"
        )

        statisticsEmbed.addEmbed(name = "Similarity", value = String.format("%.4f", scoresSimilarityRecord.get(SCORES_SIMILARITY.SIMILARITY)))
        statisticsEmbed.addEmbed(name = "Correlation", value = String.format("%.4f", scoresSimilarityRecord.get(SCORES_SIMILARITY.CORRELATION)))
        val beatmapString = "${scoresSimilarityRecord.get(BEATMAPS.TITLE)} by ${scoresSimilarityRecord.get(BEATMAPS.ARTIST)} (${
            scoresSimilarityRecord.get(BEATMAPS.VERSION)
        } | ★${String.format("%.2f", scoresSimilarityRecord.get(BEATMAPS.STAR_RATING))})"
        statisticsEmbed.addEmbed(
            name = "Beatmap",
            value = "[$beatmapString](https://osu.ppy.sh/beatmaps/${scoresSimilarityRecord.get(BEATMAPS.BEATMAP_ID)}?mode=osu)"
        )

        val embedResult = this.discordService.sendEmbeds(
            this.webhookUrl,
            listOf(statisticsEmbed)
        )

        if(embedResult) {
            dslContext.update(SCORES_SIMILARITY)
                .set(SCORES_SIMILARITY.SENT_DISCORD_NOTIFICATION, true)
                .where(SCORES_SIMILARITY.ID.eq(scoresSimilarityRecord.get(SCORES_SIMILARITY.ID)))
                .execute()
        } else {
            this.logger.error("Failed to send discord webhook.")
        }

        // Sleep for 30 seconds between webhook sends
        Thread.sleep(30_000)
    }

    fun processSuspiciousScore(score: ScoresRecord) {
        val replayData = this.scoreService.getReplayData(replayId = score.replayId!!)
        if(replayData == null) {
            logger.error("Failed to fetch score with replayId = ${score.replayId}")
            return
        }

        val statisticsEmbed = DiscordEmbed(
            title = "New suspicious score",
            description = "[osu!web](https://osu.ppy.sh/scores/osu/${score.replayId})",
            url = "https://nise.moe/s/" + score.replayId,
        )
        statisticsEmbed.color = 0xDB6D4E
        statisticsEmbed.setImageUrl("https://assets.ppy.sh/beatmaps/${replayData.beatmap_beatmapset_id}/covers/cover.jpg")
        statisticsEmbed.setAuthor(
            name = "/nise.moe/'s discord feed", url = "https://nise.moe/", iconUrl = "https://nise.moe/assets/keisatsu-chan.png"
        )

        statisticsEmbed.addEmbed(name = "Played by", value = "[${replayData.username}](https://osu.ppy.sh/users/${replayData.user_id})")
        statisticsEmbed.addEmbed(name = "Played at", value = replayData.date)
        statisticsEmbed.addEmbed(name = "PP", value = replayData.pp.roundToInt().toString())
        if(replayData.mods.isNotEmpty())
            statisticsEmbed.addEmbed(name = "Mods", value = replayData.mods.joinToString(""))
        statisticsEmbed.addEmbed(name = "Max Combo", value = "${replayData.max_combo}x")
        statisticsEmbed.addEmbed(name = "Accuracy", value = String.format("%.2f", replayData.calculateAccuracy()) + "%")
        statisticsEmbed.addEmbed(name = "cvUR | Adj. cvUR", value = String.format("%.2f", replayData.ur) + " | " + String.format("%.2f", replayData.adjusted_ur))
        val beatmapString = "${replayData.beatmap_title} by ${replayData.beatmap_artist} (${replayData.beatmap_version} | ★${
            String.format(
                "%.2f",
                replayData.beatmap_star_rating
            )
        })"
        statisticsEmbed.addEmbed(name = "Beatmap", value = "[$beatmapString](https://osu.ppy.sh/beatmaps/${replayData.beatmap_id}?mode=osu)")

        val embedResult = this.discordService.sendEmbeds(
            this.webhookUrl,
            listOf(statisticsEmbed)
        )

        if(embedResult) {
            dslContext.update(SCORES)
                .set(SCORES.SENT_DISCORD_NOTIFICATION, true)
                .where(SCORES.ID.eq(score.id))
                .execute()
        } else {
            this.logger.error("Failed to send discord webhook.")
        }

        // Sleep for 30 seconds between webhook sends
        Thread.sleep(30_000)
    }

}