/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2024 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.ResponseError
import de.steamwar.plugins.SWAuthPrincipal
import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.sql.*
import de.steamwar.sql.NodeData.SchematicFormat
import dev.dewy.nbt.Nbt
import dev.dewy.nbt.tags.collection.CompoundTag
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
import java.io.BufferedInputStream
import java.io.ByteArrayInputStream
import java.io.DataInputStream
import java.security.MessageDigest
import java.time.Duration
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.*
import java.util.zip.GZIPInputStream

@Serializable
data class ResponseSchematic(val name: String, val id: Int, val type: String?, val owner: Int, val item: String, val lastUpdate: Long, val rank: Int, val replaceColor: Boolean, val allowReplay: Boolean) {
    constructor(node: SchematicNode) : this(node.name, node.id, node.schemtype?.name(), node.owner, node.item, node.lastUpdate.time, node.rank, node.replaceColor(), node.allowReplay())
}

@Serializable
data class ResponseSchematicLong(val members: List<ResponseUser>, val path: String, val schem: ResponseSchematic) {
    constructor(node: SchematicNode, path: String): this(NodeMember.getNodeMembers(node.id).map { ResponseUser.get(it.member) }, path, ResponseSchematic(node))
}

@Serializable
data class ResponseSchematicList(val breadcrumbs: List<ResponseBreadcrumb>, val schematics: List<ResponseSchematic>, val players: Map<String, ResponseUser>) {
    constructor(schematics: List<ResponseSchematic>, breadcrumbs: List<ResponseBreadcrumb>) : this(breadcrumbs, schematics, schematics.map { it.owner }.distinct().map { ResponseUser.get(it) }.associateBy { it.uuid })
}

@Serializable
data class ResponseBreadcrumb(val name: String, val id: Int)

fun generateCode(): String {
    val md = MessageDigest.getInstance("SHA-256")
    val random = ByteArray(64).map { (0..255).random().toByte() }.toByteArray()
    val code = md.digest(random)

    return code.joinToString("") { "%02x".format(it) }
}

@Serializable
data class SchematicCode(val id: Int, val code: String, val expires: Long)

@Serializable
data class UploadSchematic(val name: String, val content: String)

val nbt = Nbt()

fun Route.configureSchematic() {
    route("/download/{code}") {
        get {
            val node = call.receiveSchematic() ?: return@get

            val user = call.principal<SWAuthPrincipal>()?.user
            if(user != null && !node.accessibleByUser(user)) {
                call.respond(HttpStatusCode.Forbidden)
                SWException.log("User ${user.userName} tried to download schematic ${node.name} without permission", user.id.toString())
                return@get
            }

            val data = NodeData.get(node) ?: run {
                call.respond(HttpStatusCode.InternalServerError)
                return@get
            }

            call.response.header("Content-Disposition", "attachment; filename=\"${node.name}${data.nodeFormat.fileEnding}\"")
            call.respondBytes(data.schemData(false).readAllBytes(), contentType = ContentType.Application.OctetStream, status = HttpStatusCode.OK)
        }
        get("/info") {
            val node = call.receiveSchematic() ?: return@get

            call.respond(ResponseSchematic(node))
        }
    }
    route("/schem") {
        install(SWPermissionCheck)

        post {
            val file = call.receive<UploadSchematic>()
            val schemName = file.name.substringBeforeLast(".")

            if (SchematicNode.invalidSchemName(arrayOf(schemName))) {
                call.respond(HttpStatusCode.BadRequest, ResponseError(
                    error = "INVALID_NAME"
                ))
                return@post
            }

            val schemType = file.name.substringAfterLast(".")

            if (schemType != "schem" && schemType != "schematic") {
                call.respond(HttpStatusCode.BadRequest, ResponseError(
                    error = "INVALID_SUFFIX"
                ))
                return@post
            }

            val user = call.principal<SWAuthPrincipal>()!!.user

            var node = SchematicNode.getSchematicNode(user.id, schemName, null as Int?)
            if (node == null) {
                node = SchematicNode.createSchematic(user.id, schemName, null)
            }

            try {
                val content = Base64.getDecoder().decode(file.content)

                var schem = nbt.fromStream(DataInputStream(BufferedInputStream(GZIPInputStream(content.inputStream()))))

                if (schem.size() == 1) schem = schem.first() as CompoundTag

                val version = schem.let {
                    if (it.contains("Materials"))
                        return@let SchematicFormat.MCEDIT
                    else if (it.contains("Blocks"))
                        return@let SchematicFormat.SPONGE_V3
                    else
                        return@let SchematicFormat.SPONGE_V2
                }

                val data = NodeData(node.id, version)
                data.saveFromStream(content.inputStream(), version)

                call.respond(ResponseSchematic(node))
            } catch (e: Exception) {
                call.respond(HttpStatusCode.BadRequest, ResponseError(
                    error = e.message ?: "GENERIC", code = "UPLOAD_ERROR"
                ))
            }
        }
    }
}

suspend fun ApplicationCall.receiveSchematic(fieldName: String = "code", delete: Boolean = false): SchematicNode? {
    val code = parameters[fieldName] ?: run {
        respond(HttpStatusCode.BadRequest)
        return null
    }

    val dl = NodeDownload.get(code) ?: run {
        respond(HttpStatusCode.NotFound)
        return null
    }

    if(dl.timestamp.toInstant().plus(Duration.of(5, ChronoUnit.MINUTES)).isBefore(Instant.now())) {
        respond(HttpStatusCode.Gone)
        return null
    }

    if (delete) {
        dl.delete()
    }

    val node = SchematicNode.getSchematicNode(dl.nodeId) ?: run {
        respond(HttpStatusCode.NotFound)
        return null
    }

    return node
}
