package de.hoennig.werkator.commands

import de.hoennig.werkator.git.GitService
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.booleans.shouldBeFalse
import io.kotest.matchers.file.shouldExist
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.mockk.every
import io.mockk.mockk
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.attribute.PosixFilePermissions

class InitCommandTest : FunSpec() {
    private val gitService = mockk<GitService>()
    private val initCommand =
        InitCommand(
            gitService,
            // default (null) BuildProperties provider: a relaxed ObjectProvider mock
            // returns a raw Object under type erasure and breaks the version check
            de.hoennig.werkator.config
                .ConfigLoader(),
        )

    init {
        test("creates config files with auto-detected values") {
            val tempDir = Files.createTempDirectory("werkator-init-test")
            initCommand.workingDir = tempDir

            every { gitService.getTopLevel(tempDir) } returns tempDir
            every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"

            initCommand.run()

            val projectConfig = tempDir.resolve(".werkator.yml")
            projectConfig.toFile().shouldExist()
            val projectContent = projectConfig.toFile().readText()
            projectContent shouldContain "baseUrl: https://git.example.org"
            projectContent shouldContain "owner: my-org"
            projectContent shouldContain "repo: my-repo"

            val repoConfig = tempDir.resolve(".git/werkator/.werkator.yml")
            repoConfig.toFile().shouldExist()
            val repoContent = repoConfig.toFile().readText()
            repoContent shouldContain "account: \"\"" // no user in https URL
        }

        test("does not write a second config beside one under the previous name") {
            val tempDir = Files.createTempDirectory("werkator-init-test")
            initCommand.workingDir = tempDir
            Files.createDirectories(tempDir.resolve(".git/gittally"))
            tempDir.resolve(".gittally.yml").toFile().writeText("gitea:\n  owner: kept\n")
            tempDir.resolve(".git/gittally/.gittally.yml").toFile().writeText("git:\n  account: kept\n")

            every { gitService.getTopLevel(tempDir) } returns tempDir
            every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"

            initCommand.run()

            // a second file would shadow the first one, and the repository would
            // silently build something else than what its configuration says
            Files.exists(tempDir.resolve(".werkator.yml")).shouldBeFalse()
            Files.exists(tempDir.resolve(".git/werkator/.werkator.yml")).shouldBeFalse()
            tempDir.resolve(".gittally.yml").toFile().readText() shouldContain "owner: kept"
        }

        test("creates the secrets config and its directory readable only by the owner") {
            val tempDir = Files.createTempDirectory("werkator-init-test")
            initCommand.workingDir = tempDir

            every { gitService.getTopLevel(tempDir) } returns tempDir
            every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"

            initCommand.run()

            val repoConfig = tempDir.resolve(".git/werkator/.werkator.yml")
            PosixFilePermissions.toString(Files.getPosixFilePermissions(repoConfig)) shouldBe "rw-------"
            PosixFilePermissions.toString(Files.getPosixFilePermissions(repoConfig.parent)) shouldBe "rwx------"
        }

        test("detects account from https url") {
            val tempDir = Files.createTempDirectory("werkator-init-test")
            initCommand.workingDir = tempDir

            every { gitService.getTopLevel(tempDir) } returns tempDir
            every { gitService.getOriginUrl(tempDir) } returns "https://ci-user@git.example.org/my-org/my-repo.git"

            initCommand.run()

            val repoConfig = tempDir.resolve(".git/werkator/.werkator.yml")
            val repoContent = repoConfig.toFile().readText()
            repoContent shouldContain "account: \"ci-user\""
        }

        test("parses ssh url") {
            val tempDir = Files.createTempDirectory("werkator-init-test")
            initCommand.workingDir = tempDir

            every { gitService.getTopLevel(tempDir) } returns tempDir
            every { gitService.getOriginUrl(tempDir) } returns "git@git.example.org:my-org/my-repo.git"

            initCommand.run()

            val projectConfig = tempDir.resolve(".werkator.yml")
            val projectContent = projectConfig.toFile().readText()
            projectContent shouldContain "baseUrl: https://git.example.org" // fallback to https
            projectContent shouldContain "owner: my-org"
            projectContent shouldContain "repo: my-repo"
        }

        test("keeps dots in repository names") {
            val tempDir = Files.createTempDirectory("werkator-init-test")
            initCommand.workingDir = tempDir

            every { gitService.getTopLevel(tempDir) } returns tempDir
            every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/mi/michael.hoennig.de.git"

            initCommand.run()

            val projectConfig = tempDir.resolve(".werkator.yml")
            val projectContent = projectConfig.toFile().readText()
            projectContent shouldContain "owner: mi"
            projectContent shouldContain "repo: michael.hoennig.de"
        }

        test("does not overwrite existing files") {
            val tempDir = Files.createTempDirectory("werkator-init-test")
            initCommand.workingDir = tempDir

            val projectConfig = tempDir.resolve(".werkator.yml")
            projectConfig.toFile().writeText("existing: content")

            every { gitService.getTopLevel(tempDir) } returns tempDir
            every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"

            initCommand.run()

            projectConfig.toFile().readText() shouldBe "existing: content"
        }

        test("--apply installs the fragment as the applied layer and the effective config sees it") {
            val tempDir = Files.createTempDirectory("werkator-init-test")
            initCommand.workingDir = tempDir
            val fragment = tempDir.resolve("mih34.yml")
            fragment.toFile().writeText("server:\n  port: 18088\n")
            initCommand.apply = fragment

            every { gitService.getTopLevel(tempDir) } returns tempDir
            every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"

            initCommand.run()

            tempDir
                .resolve(de.hoennig.werkator.config.ConfigFiles.APPLIED)
                .toFile()
                .shouldExist()
            de.hoennig.werkator.config
                .ConfigLoader()
                .load(tempDir)
                .server.port shouldBe 18088
            initCommand.apply = null
        }

        test("--apply with an invalid fragment installs nothing") {
            val tempDir = Files.createTempDirectory("werkator-init-test")
            initCommand.workingDir = tempDir
            val fragment = tempDir.resolve("typo.yml")
            fragment.toFile().writeText("server:\n  prot: 18088\n")
            initCommand.apply = fragment

            every { gitService.getTopLevel(tempDir) } returns tempDir
            every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"

            initCommand.run()

            Files
                .exists(tempDir.resolve(de.hoennig.werkator.config.ConfigFiles.APPLIED))
                .shouldBeFalse()
            initCommand.apply = null
        }

        test("--systemd generates unit and environment file with install instructions") {
            val tempDir = Files.createTempDirectory("werkator-init-test")
            initCommand.workingDir = tempDir
            initCommand.systemd = true
            initCommand.jarPathResolver = { Paths.get("/home/ci/bin/werkator.jar") }
            initCommand.javaExecutableResolver = { Paths.get("/usr/bin/java") }

            every { gitService.getTopLevel(tempDir) } returns tempDir
            every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"

            initCommand.run()

            val unitName = SystemdServiceFiles.unitName(tempDir)
            val unitFile = tempDir.resolve(".git/werkator/$unitName")
            unitFile.toFile().shouldExist()
            val unitContent = unitFile.toFile().readText()
            unitContent shouldContain "WorkingDirectory=$tempDir"
            unitContent shouldContain """ExecStart="/usr/bin/java" ${'$'}JAVA_OPTS -jar "/home/ci/bin/werkator.jar" server"""

            tempDir.resolve(".git/werkator/werkator.env").toFile().shouldExist()
        }

        test("--systemd also generates the nightly Docker cleanup timer") {
            val tempDir = Files.createTempDirectory("werkator-init-test")
            initCommand.workingDir = tempDir
            initCommand.systemd = true
            initCommand.jarPathResolver = { Paths.get("/home/ci/bin/werkator.jar") }
            initCommand.javaExecutableResolver = { Paths.get("/usr/bin/java") }

            every { gitService.getTopLevel(tempDir) } returns tempDir
            every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"

            initCommand.run()

            val pruneService = tempDir.resolve(".git/werkator/werkator-docker-prune.service")
            pruneService.toFile().shouldExist()
            pruneService.toFile().readText() shouldContain "docker system prune -af"
            val pruneTimer = tempDir.resolve(".git/werkator/werkator-docker-prune.timer")
            pruneTimer.toFile().shouldExist()
            pruneTimer.toFile().readText() shouldContain "OnCalendar=*-*-* 02:00:00"
        }

        test("--systemd keeps an existing environment file") {
            val tempDir = Files.createTempDirectory("werkator-init-test")
            initCommand.workingDir = tempDir
            initCommand.systemd = true
            initCommand.jarPathResolver = { Paths.get("/home/ci/bin/werkator.jar") }
            initCommand.javaExecutableResolver = { Paths.get("/usr/bin/java") }

            val envFile = tempDir.resolve(".git/werkator/werkator.env")
            Files.createDirectories(envFile.parent)
            envFile.toFile().writeText("JAVA_OPTS=-Xmx1g\n")

            every { gitService.getTopLevel(tempDir) } returns tempDir
            every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"

            initCommand.run()

            envFile.toFile().readText() shouldBe "JAVA_OPTS=-Xmx1g\n"
        }

        test("--systemd without a resolvable jar path generates no unit file") {
            val tempDir = Files.createTempDirectory("werkator-init-test")
            initCommand.workingDir = tempDir
            initCommand.systemd = true
            initCommand.jarPathResolver = { null }

            every { gitService.getTopLevel(tempDir) } returns tempDir
            every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"

            initCommand.run()

            val unitFile = tempDir.resolve(".git/werkator/${SystemdServiceFiles.unitName(tempDir)}")
            unitFile.toFile().exists() shouldBe false
        }

        test("reproduces path root mismatch issue") {
            val tempDir = Files.createTempDirectory("werkator-init-test").toAbsolutePath().normalize()
            initCommand.workingDir = Paths.get(".") // Set to relative path as in real app

            // We need to mock getTopLevel to return the absolute path
            every { gitService.getTopLevel(any()) } returns tempDir
            every { gitService.getOriginUrl(any()) } returns "https://git.example.org/my-org/my-repo.git"

            // This should not throw IllegalArgumentException
            initCommand.run()
        }

        test("--systemd reads the configuration from the repository root, not the current directory") {
            val tempDir = Files.createTempDirectory("werkator-init-test")
            // written before the run, so `init` keeps it instead of creating a template
            tempDir.resolve(".werkator.yml").toFile().writeText(
                "server:\n  publicBaseUrl: \"https://werkator.example.org/\"\n  port: 18099\n",
            )
            initCommand.workingDir = tempDir
            initCommand.systemd = true
            initCommand.jarPathResolver = { Paths.get("/home/ci/bin/werkator.jar") }
            initCommand.javaExecutableResolver = { Paths.get("/usr/bin/java") }

            every { gitService.getTopLevel(tempDir) } returns tempDir
            every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"

            initCommand.run()

            // the host integration is generated only when the root's config has a public base URL
            val htaccess = tempDir.resolve(".git/werkator/${SystemdServiceFiles.HTACCESS_NAME}")
            htaccess.toFile().shouldExist()
            htaccess.toFile().readText() shouldContain "18099"
            tempDir.resolve(".git/werkator/${SystemdServiceFiles.MAINTENANCE_PAGE_NAME}").toFile().shouldExist()
        }

        test("--systemd warns once when the effective configuration cannot be loaded") {
            val tempDir = Files.createTempDirectory("werkator-init-test")
            val brokenLoader = mockk<de.hoennig.werkator.config.ConfigLoader>()
            every { brokenLoader.load(any()) } throws IllegalStateException("gitea.owner is required")
            val command = InitCommand(gitService, brokenLoader)
            command.workingDir = tempDir
            command.systemd = true
            command.jarPathResolver = { Paths.get("/home/ci/bin/werkator.jar") }
            command.javaExecutableResolver = { Paths.get("/usr/bin/java") }

            every { gitService.getTopLevel(tempDir) } returns tempDir
            every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git"

            val console = captureConsole { command.run() }

            console.stdout shouldContain "gitea.owner is required"
            // the three readers of the configuration must not repeat the warning
            console.stdout.windowed("Warning:".length).count { it == "Warning:" } shouldBe 1
            // the units are still written with the defaults
            tempDir.resolve(".git/werkator/${SystemdServiceFiles.unitName(tempDir)}").toFile().shouldExist()
        }
    }
}
