/*
 *   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.config
import de.steamwar.plugins.SWAuthPrincipal
import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.sql.UserPerm
import io.ktor.client.*
import io.ktor.client.engine.java.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
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 kotlinx.serialization.json.*
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.*

val pathPageIdMap = mutableMapOf<String, Int>()
var pageId = 1

@Serializable
data class Identity(val name: String, val email: String)

@Serializable
data class PageResponseList(
    val path: String,
    val name: String,
    val sha: String,
    val downloadUrl: String,
    val id: Int
) {
    constructor(res: JsonObject, id: Int) : this(
        res["path"]?.jsonPrimitive?.content!!,
        res["name"]?.jsonPrimitive?.content!!,
        res["sha"]?.jsonPrimitive?.content!!,
        res["download_url"]?.jsonPrimitive?.content!!,
        id
    )
}

@Serializable
data class PageResponse(
    val path: String,
    val name: String,
    val sha: String,
    val downloadUrl: String,
    val content: String,
    val size: Int,
    val id: Int,
) {
    constructor(res: JsonObject, id: Int) : this(
        res["path"]?.jsonPrimitive?.content!!,
        res["name"]?.jsonPrimitive?.content!!,
        res["sha"]?.jsonPrimitive?.content!!,
        res["download_url"]?.jsonPrimitive?.content!!,
        res["content"]?.jsonPrimitive?.content!!,
        res["size"]?.jsonPrimitive?.int!!,
        id
    )
}

@Serializable
data class CreatePageRequest(val path: String, val slug: String?, val title: String?)

@Serializable
data class AddImageRequest(val name: String, val data: String)

@Serializable
data class CreateBranchRequest(val branch: String)

@Serializable
data class UpdatePageRequest(val content: String, val sha: String, val message: String)

@Serializable
data class MergeBranchRequest(val branch: String, val message: String)

@Serializable
data class DeletePageRequest(val sha: String, val message: String)

@Serializable
data class CreateGiteaPageRequest(val message: String, val content: String, val branch: String, val author: Identity)

fun Route.configurePage() {
    val client = HttpClient(Java) {
        install(ContentNegotiation) {
            json()
        }
        defaultRequest {
            url("https://git.steamwar.de/api/v1/")
            header("Authorization", "token " + config.giteaToken)
        }
    }

    suspend fun filesInDirectory(path: String, branch: String = "master", fileFilter: (name: String) -> Boolean = { true }): List<PageResponseList> {
        val filesToCheck = mutableListOf(path)
        val files = mutableListOf<PageResponseList>()

        while (filesToCheck.isNotEmpty()) {
            val path = filesToCheck.removeAt(0)
            val res = client.get("repos/SteamWar/Website/contents/$path?ref=$branch")
            val fileJson = Json.parseToJsonElement(res.bodyAsText())

            if (fileJson is JsonArray) {
                fileJson.forEach {
                    val obj = it.jsonObject
                    if (obj["type"]?.jsonPrimitive?.content == "dir") {
                        filesToCheck.add(obj["path"]?.jsonPrimitive?.content!!)
                    } else if (obj["type"]?.jsonPrimitive?.content == "file" && fileFilter(obj["name"]!!.jsonPrimitive.content)) {
                        files.add(PageResponseList(obj, pathPageIdMap.computeIfAbsent(obj["path"]?.jsonPrimitive?.content!!) { pageId++ }))
                    }
                }
            } else {
                files.add(PageResponseList(fileJson.jsonObject, pathPageIdMap.computeIfAbsent(fileJson.jsonObject["path"]?.jsonPrimitive?.content!!) { pageId++ }))
            }
        }

        return files
    }

    route("page") {
        install(SWPermissionCheck) {
            permission = UserPerm.MODERATION
        }
        get {
            val branch = call.request.queryParameters["branch"]
                ?: "master"

            call.respond(filesInDirectory("/src/content", branch) {
                it.endsWith(".md") || it.endsWith(".json")
            })
        }
        post {
            val req = call.receive<CreatePageRequest>()
            if (req.path.startsWith("src/content/")) {
                call.respond(HttpStatusCode.BadRequest, "Invalid path")
                return@post
            }
            val res = client.post("repos/SteamWar/Website/contents/src/content/${req.path}") {
                contentType(ContentType.Application.Json)
                setBody(
                    CreateGiteaPageRequest(
                        "Create page ${req.path}",
                        Base64.getEncoder().encodeToString(
                            (
                                    if (req.path.endsWith(".md")) """
                        ---
                        title: ${req.title?.removeSuffix(".md") ?: "Enter Title"}
                        description: Enter Description
                        key: ${req.slug?.lowercase()?.removeSuffix(".md") ?: "Enter Slug"}
                        created: ${LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE)}
                        tags: 
                        - test
                        ---
                        
                        # ${req.path}
                    """ else "{}"
                                    ).trimIndent().toByteArray()
                        ),
                        call.request.queryParameters["branch"]
                            ?: "master",
                        Identity(
                            call.principal<SWAuthPrincipal>()!!.user.userName, "admin-tool@steamwar.de"
                        )
                    )
                )
            }
            call.respond(res.status)
        }
        route("branch") {
            get {
                val res = client.get("repos/SteamWar/Website/branches")
                call.respond(res.status, Json.parseToJsonElement(res.bodyAsText()).jsonArray.map { it.jsonObject["name"]?.jsonPrimitive?.content!! })
            }
            post {
                @Serializable
                data class CreateGiteaBranchRequest(val new_branch_name: String, val old_branch_name: String)

                val branch = call.receive<CreateBranchRequest>().branch
                val res = client.post("repos/SteamWar/Website/branches") {
                    contentType(ContentType.Application.Json)
                    setBody(CreateGiteaBranchRequest(branch, "master"))
                }


                @Serializable
                data class CreateGiteaMergeRequest(val base: String, val head: String, val title: String)

                client.post("repos/SteamWar/Website/pulls") {
                    contentType(ContentType.Application.Json)
                    setBody(CreateGiteaMergeRequest("master", branch, "Merge branch $branch"))
                }

                call.respond(res.status)
            }
            delete {
                val branch = call.receive<CreateBranchRequest>().branch
                val res = client.delete("repos/SteamWar/Website/branches/$branch")
                call.respond(res.status)
            }
        }
        route("{id}") {
            get {
                val id = call.parameters["id"]?.toIntOrNull()
                    ?: return@get call.respond(HttpStatusCode.BadRequest, "Invalid id")
                val path = pathPageIdMap.entries.find { it.value == id }?.key
                    ?: return@get call.respond(HttpStatusCode.NotFound, "Page not found")

                val branch = call.request.queryParameters["branch"]
                    ?: "master"
                val res = client.get("repos/SteamWar/Website/contents/$path?ref=$branch")
                val fileJson = Json.parseToJsonElement(res.bodyAsText())
                if (fileJson is JsonArray) {
                    return@get call.respond(HttpStatusCode.BadRequest, "Invalid id")
                }

                val file = PageResponse(fileJson.jsonObject, id)
                call.respond(file)
            }
            delete {
                val data = call.receive<DeletePageRequest>()

                val path = pathPageIdMap.entries.find { it.value == call.parameters["id"]?.toIntOrNull() }?.key
                    ?: return@delete call.respond(HttpStatusCode.NotFound, "Page not found")
                val branch = call.request.queryParameters["branch"]
                    ?: "master"

                @Serializable
                data class DeleteGiteaPageRequest(val sha: String, val message: String, val branch: String, val author: Identity)

                val res = client.delete("repos/SteamWar/Website/contents/$path") {
                    contentType(ContentType.Application.Json)
                    setBody(DeleteGiteaPageRequest(data.sha, data.message, branch, Identity(call.principal<SWAuthPrincipal>()!!.user.userName, "admin-tool@steamwar.de")))
                }

                call.respond(res.status)
            }
            put {
                @Serializable
                data class UpdateGiteaPageRequest(val content: String, val sha: String, val message: String, val branch: String, val author: Identity)

                val data = call.receive<UpdatePageRequest>()
                val path = pathPageIdMap.entries.find { it.value == call.parameters["id"]?.toIntOrNull() }?.key
                    ?: return@put call.respond(HttpStatusCode.NotFound, "Page not found")

                val res = client.put("repos/SteamWar/Website/contents/$path") {
                    contentType(ContentType.Application.Json)
                    setBody(
                        UpdateGiteaPageRequest(
                            data.content, data.sha, data.message, (call.request.queryParameters["branch"]
                                ?: "master"), Identity(call.principal<SWAuthPrincipal>()!!.user.userName, "admin-tool@steamwar.de")
                        )
                    )
                }

                call.respond(res.status)
            }
        }
        route("images") {
            get {
                val branch = call.request.queryParameters["branch"]
                    ?: "master"

                call.respond(filesInDirectory("/src/images", branch))
            }
            post {
                val req = call.receive<AddImageRequest>()

                client.post("repos/SteamWar/Website/contents/src/images/${req.name}") {
                    contentType(ContentType.Application.Json)
                    setBody(
                        CreateGiteaPageRequest(
                            "Add Image ${req.name}",
                            req.data,
                            call.request.queryParameters["branch"]
                                ?: "master",
                            Identity(
                                call.principal<SWAuthPrincipal>()!!.user.userName, "admin-tool@steamwar.de"
                            )
                        )
                    )
                }

                call.respond(HttpStatusCode.Created)
            }
        }
    }
}