/*
 *   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/>.
 */

import java.security.MessageDigest
import java.util.stream.Collectors


plugins {
}

class DevServer extends DefaultTask {

    @Input
    @Optional
    String worldName = null

    @Input
    String template = null

    @Input
    @Optional
    String plugins = null

    @Input
    @Optional
    Integer port = null

    @Input
    @Optional
    String jar = null

    @Input
    @Optional
    Map<String, String> dParams = new HashMap<>()

    @Input
    @Optional
    String checkpointFolder = null

    @Input
    @Optional
    Boolean profile = null

    @Input
    @Optional
    Boolean forceUpgrade = null

    DevServer() {
        super()
        doFirst {
            if (checkpointFolder != null) dParams.put("checkpoint", checkpointFolder)

            List<Project> projects = []
            projects.add(project)
            while (projects.first.parent != null) {
                projects.add(0, projects.first.parent)
            }

            def properties = new Properties()
            projects.forEach {
                def file = new File(it.projectDir, "steamwar.properties")
                if (file.exists()) {
                    properties.load(new FileInputStream(file))
                }
            }

            if (worldName == null) worldName = properties.get("worldName")
            host = properties.get("host")

            if (host == null) {
                throw new GradleException("Please supply the 'host' in a 'steamwar.properties' files either in this project dir or any parent project!")
            }
        }
        doLast {
            setupTemplate(template)
            uploadDependencies()
            startDevServer()
        }
        finalizedBy(new Finalizer())
    }

    @Internal
    BufferedWriter processInput

    @Internal
    String host

    @Internal
    Boolean running = true

    class Finalizer extends DefaultTask {

        Finalizer() {
            super()
            doLast {
                running = false
                if (processInput != null) {
                    processInput.write(template.endsWith("Velocity") ? "end\n" : "stop\n")
                    processInput.flush()
                }
            }
        }
    }

    private Process run(String... args) {
        List<String> arguments = new ArrayList<>();
        arguments.add("ssh")
        arguments.add(host)
        arguments.add("-T")
        arguments.addAll(Arrays.asList(args))
        def process = new ProcessBuilder(arguments).start()
        process.waitFor()
        return process
    }

    private boolean checkFileOnRemote(String path) {
        def process = run("[ -e \"$path\" ] && echo \"true\"")
        return new BufferedReader(new InputStreamReader(process.inputStream)).lines().count() > 0
    }

    void setupTemplate(String template) {
        if (checkFileOnRemote("$template")) return
        if (checkFileOnRemote("/configs/GameModes/${template}.yml")) {
            println("GameMode Config exists")
            def process = run("cat /configs/GameModes/${template}.yml | grep \"Folder: \"")
            String serverTemplateName = new BufferedReader(new InputStreamReader(process.inputStream)).lines().collect(Collectors.joining("\n"))
                    .trim()
                    .substring("Folder: ".length())
            setupTemplate(serverTemplateName)
            run("ln -s $serverTemplateName $template")
            return
        }
        if (!checkFileOnRemote("/servers/$template")) {
            throw new GradleException("Used template ($template) is not in /servers/ directory of the given host $host")
        }
        run("cp -r /servers/$template $template")
        run("chmod u+w $template")
        run("rm -r $template/plugins/*WorldEdit/")
        run("rm $template/log4j2.xml")
    }

    void uploadDependencies() {
        def base = plugins == null ? "$template/plugins" : plugins
        println("Uploading to ~/$base")
        this.dependsOn.forEach {
            Project resolved
            AbstractArchiveTask archiveTask
            if (it instanceof String) {
                resolved = project.findProject(it.substring(0, it.lastIndexOf(':')))
                archiveTask = (AbstractArchiveTask) resolved.tasks.findByName(it.substring(it.lastIndexOf(':') + 1))
            } else {
                throw new GradleException("Illegal argument for uploading dependencies")
            }

            def archive = archiveTask.archiveFile.get().asFile

            Process process = new ProcessBuilder("ssh", host, "-T", "sha1sum $base/${archive.name.replace("-all", "")}").start();
            byte[] bytes = MessageDigest.getInstance("sha1").digest(archive.bytes)
            StringBuilder sb = new StringBuilder()
            for (byte b : bytes) {
                sb.append(String.format("%02X", b))
            }
            boolean same = false
            process.inputStream.readLines().forEach {
                same |= it.startsWith(sb.toString().toLowerCase())
            }
            if (same) {
                println("Skipping  $archive")
                return
            }

            println("Uploading $archive")
            new ProcessBuilder("ssh", host, "-T", "rm $base/${archive.name.replace("-all", "")}").start().waitFor()
            new ProcessBuilder("scp", archive.absolutePath, "$host:~/$base/${archive.name.replace("-all", "")}").start().waitFor()
            println("Uploaded  $archive")
        }
    }

    void startDevServer() {
        def devPy = new StringBuilder().append("dev.py")
        if (port != null) devPy.append(" --port $port")
        if (worldName != null) devPy.append(" -w $template/$worldName")
        if (plugins != null) devPy.append(" -p $plugins")
        if (profile != null) devPy.append(" --profile")
        if (forceUpgrade != null) devPy.append(" --forceUpgrade")
        if (jar != null) devPy.append(" --jar $jar")
        for (Map.Entry<String, String> dParam : dParams.entrySet()) {
            devPy.append(" -D${dParam.key}=${dParam.value}")
        }
        devPy.append(" $template")
        println("Starting $template with command ${devPy.toString()}")

        def process = new ProcessBuilder("ssh", host, "-T", devPy.toString()).start()
        def processOutput = new BufferedReader(new InputStreamReader(process.inputStream))
        new Thread({
            while (running) {
                if (processOutput.ready()) {
                    println(processOutput.readLine())
                }
            }
        }).start()

        processInput = new BufferedWriter(new OutputStreamWriter(process.outputStream))
        def input = new BufferedReader(new InputStreamReader(System.in))
        new Thread({
            while (running) {
                def text = input.readLine()
                if (text == null) break
                processInput.write(text)
                processInput.newLine()
                processInput.flush()
            }
        }).start()

        process.waitFor()
        processInput = null
        running = false
    }
}

class FightServer extends DevServer {

    @Input
    @Optional
    Integer checkSchemID = 0

    @Input
    @Optional
    Integer prepareSchemID = 0

    @Input
    @Optional
    Integer replay = 0

    @Input
    @Optional
    String config = null

    @Input
    @Optional
    // Property: fightID
    Integer eventKampfID = 0

    @Input
    @Optional
    UUID blueLeader = null

    @Input
    @Optional
    UUID redLeader = null

    FightServer() {
        super()
        doFirst {
            if (checkSchemID != 0) dParams.put("checkSchemID", "$checkSchemID")
            if (prepareSchemID != 0) dParams.put("prepareSchemID", "$prepareSchemID")
            if (replay != 0) dParams.put("replay", "$replay")
            if (eventKampfID != 0) dParams.put("fightID", "$eventKampfID")
            if (blueLeader != null) dParams.put("blueLeader", blueLeader.toString())
            if (redLeader != null) dParams.put("redLeader", redLeader.toString())
            if (config != null) dParams.put("config", config)
        }
    }
}