/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.routes

import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.sql.AuditLog
import de.steamwar.sql.AuditLogTable
import de.steamwar.sql.SteamwarUserTable
import de.steamwar.sql.UserPerm
import de.steamwar.sql.internal.useDb
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
import org.jetbrains.exposed.v1.core.*
import org.jetbrains.exposed.v1.jdbc.Query
import org.jetbrains.exposed.v1.jdbc.andWhere
import org.jetbrains.exposed.v1.jdbc.select
import org.jetbrains.exposed.v1.jdbc.selectAll
import java.time.Instant

fun Route.configureAuditLog() {
    route("/auditlog") {
        install(SWPermissionCheck) {
            mustAuth = true
            permission = UserPerm.MODERATION
        }

        get {
            val text = call.request.queryParameters["fullText"]
            val actionText = call.request.queryParameters["actionText"]
            val serverText = call.request.queryParameters["server"]
            val actor = call.request.queryParameters.getAll("actor")?.map { it.toInt() }?.toSet()
            val actionType =
                call.request.queryParameters.getAll("actionType")?.map { AuditLog.Type.valueOf(it) }?.toSet()
            val timeGreater = call.request.queryParameters["timeGreater"]?.let { Instant.ofEpochMilli(it.toLong()) }
            val timeLess = call.request.queryParameters["timeLess"]?.let { Instant.ofEpochMilli(it.toLong()) }

            val serverOwner = call.request.queryParameters.getAll("serverOwner")?.map { it.toInt() }?.toSet()
            val velocity = call.request.queryParameters["velocity"]?.toBoolean()

            val page = call.request.queryParameters["page"]?.toIntOrNull()
                ?: 0
            val limit = call.request.queryParameters["limit"]?.toIntOrNull()
                ?: 100
            val sorting = call.request.queryParameters["sorting"]
                ?: "DESC"

            call.respond(
                PagedAuditLog.filter(
                    actionText,
                    serverText,
                    text,
                    actor,
                    actionType,
                    timeGreater,
                    timeLess,
                    serverOwner,
                    velocity,
                    page,
                    limit,
                    sorting
                )
            )
        }
    }
}

@Serializable
data class PagedAuditLog(val rows: Long, val entries: List<AuditLogEntry>) {

    companion object {
        fun filter(
            actionText: String? = null,
            serverText: String? = null,
            fullText: String? = null,
            actor: Set<Int>? = null,
            actionType: Set<AuditLog.Type>? = null,
            timeGreater: Instant? = null,
            timeLess: Instant? = null,
            serverOwner: Set<Int>? = null,
            velocity: Boolean? = null,
            page: Int = 0,
            limit: Int = 100,
            sorting: String = "DESC"
        ) =
            useDb {
                val actorTable = SteamwarUserTable.alias("actor")
                val serverOwnerTable = SteamwarUserTable.alias("serverOwner")

                val query = AuditLogTable.join(
                    actorTable,
                    JoinType.INNER,
                    onColumn = actorTable[SteamwarUserTable.id],
                    otherColumn = AuditLogTable.actor
                )
                    .join(
                        serverOwnerTable,
                        JoinType.LEFT,
                        onColumn = serverOwnerTable[SteamwarUserTable.id],
                        otherColumn = AuditLogTable.serverOwner
                    )
                    .select(
                        actorTable[SteamwarUserTable.username], serverOwnerTable[SteamwarUserTable.username],
                        *AuditLogTable.columns.toTypedArray()
                    )

                PagedAuditLog(
                    AuditLogTable.selectAll().addAuditLogFilters(
                        actionText,
                        serverText,
                        fullText,
                        actor,
                        actionType,
                        timeGreater,
                        timeLess,
                        serverOwner,
                        velocity
                    ).count(),
                    query
                        .addAuditLogFilters(
                            actionText,
                            serverText,
                            fullText,
                            actor,
                            actionType,
                            timeGreater,
                            timeLess,
                            serverOwner,
                            velocity
                        )
                        .limit(limit)
                        .offset((page * limit).toLong())
                        .orderBy(AuditLogTable.time, SortOrder.valueOf(sorting.uppercase()))
                        .map {
                            AuditLogEntry(
                                it[AuditLogTable.id].value,
                                it[AuditLogTable.time].toEpochMilli(),
                                it[AuditLogTable.server],
                                it[serverOwnerTable[SteamwarUserTable.username]],
                                it[actorTable[SteamwarUserTable.username]],
                                it[AuditLogTable.action],
                                it[AuditLogTable.actionText]
                            )
                        },
                )
            }

        private fun Query.addAuditLogFilters(
            actionText: String? = null,
            serverText: String? = null,
            fullText: String? = null,
            actor: Set<Int>? = null,
            actionType: Set<AuditLog.Type>? = null,
            timeGreater: Instant? = null,
            timeLess: Instant? = null,
            serverOwner: Set<Int>? = null,
            velocity: Boolean? = null,
        ): Query {
            actionText?.let {
                andWhere { (AuditLogTable.actionText like "%$it%") }
            }
            serverText?.let {
                andWhere { (AuditLogTable.server like "%$it%") }
            }
            fullText?.let {
                andWhere { (AuditLogTable.actionText like "%$it%") or (AuditLogTable.server like "%$it%") }
            }
            actor?.let { andWhere { AuditLogTable.actor inList actor } }
            actionType?.let { andWhere { AuditLogTable.action inList actionType } }
            timeGreater?.let { andWhere { AuditLogTable.time greater timeGreater } }
            timeLess?.let { andWhere { AuditLogTable.time less timeLess } }
            serverOwner?.let { andWhere { AuditLogTable.serverOwner inList serverOwner } }
            if (velocity == true) andWhere { AuditLogTable.serverOwner.isNull() }

            return this
        }
    }
}

@Serializable
data class AuditLogEntry(
    val id: Int,
    val time: Long,
    val server: String,
    val serverOwner: String?,
    val actor: String,
    val actionType: AuditLog.Type,
    val actionText: String
)