package de.hoennig.werkator.server

import de.hoennig.werkator.config.GiteaConfig
import de.hoennig.werkator.metrics.DiskSource
import de.hoennig.werkator.metrics.MetricAggregate
import de.hoennig.werkator.metrics.SystemMetrics
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import java.time.Duration
import java.time.Instant

private fun metricsWithDisk(
    diskTotalGib: Double,
    diskSource: DiskSource? = null,
    quotasPresent: Boolean = false,
) = SystemMetrics(
    timestamp = Instant.parse("2026-09-03T12:00:00Z"),
    sampleCount = 1,
    cpuCount = 4,
    ramTotalGib = 8.0,
    diskTotalGib = diskTotalGib,
    diskSource = diskSource,
    quotasPresent = quotasPresent,
    cpuUsed = null,
    cpuIdle = null,
    ramUsedGib = null,
    ramFreeGib = null,
    diskUsedGib = null,
    diskFreeGib = null,
    repoSizeGib = null,
)

class UiViewsTest : FunSpec() {
    init {
        test("durations format as m:ss and h:mm:ss like the legacy duration column") {
            UiFormats.duration(null) shouldBe ""
            UiFormats.duration(Duration.ofSeconds(0)) shouldBe "0:00"
            UiFormats.duration(Duration.ofSeconds(83)) shouldBe "1:23"
            UiFormats.duration(Duration.ofSeconds(3600 + 62)) shouldBe "1:01:02"
        }

        test("metric values format with two decimals and a dot, n/a when unavailable") {
            UiFormats.metric(null) shouldBe "n/a"
            UiFormats.metric(Double.NaN) shouldBe "n/a"
            UiFormats.metric(0.0) shouldBe "0.00"
            UiFormats.metric(1.234) shouldBe "1.23"
            UiFormats.metric(31.288) shouldBe "31.29"
        }

        test("utilization highlights warn from 80% and crit from 90% of the total") {
            UiFormats.utilizationClass(7.9, 10.0) shouldBe ""
            UiFormats.utilizationClass(8.0, 10.0) shouldBe "metric-warn"
            UiFormats.utilizationClass(8.9, 10.0) shouldBe "metric-warn"
            UiFormats.utilizationClass(9.0, 10.0) shouldBe "metric-crit"
            UiFormats.utilizationClass(11.0, 10.0) shouldBe "metric-crit"
        }

        test("utilization highlighting is off when a value or the total is unavailable") {
            UiFormats.utilizationClass(null, 10.0) shouldBe ""
            UiFormats.utilizationClass(9.5, null) shouldBe ""
            UiFormats.utilizationClass(Double.NaN, 10.0) shouldBe ""
            UiFormats.utilizationClass(9.5, 0.0) shouldBe ""
        }

        test("only the used rows with a total get the critical highlighting") {
            val aggregate = { value: Double -> MetricAggregate(current = value, min = 0.0, max = value, avg = value) }
            val view =
                SystemMetricsView.from(
                    SystemMetrics(
                        timestamp = Instant.parse("2026-08-10T12:00:00Z"),
                        sampleCount = 1,
                        cpuCount = 4,
                        ramTotalGib = 8.0,
                        diskTotalGib = 100.0,
                        cpuUsed = aggregate(3.7),
                        cpuIdle = aggregate(0.3),
                        ramUsedGib = aggregate(6.5),
                        ramFreeGib = aggregate(1.5),
                        diskUsedGib = aggregate(70.0),
                        diskFreeGib = aggregate(30.0),
                        repoSizeGib = aggregate(95.0),
                    ),
                )

            val classesByKey = view.rows.associate { it.key to it.currentClass }
            classesByKey["cpuUsed"] shouldBe "metric-crit"
            classesByKey["ramUsedGib"] shouldBe "metric-warn"
            classesByKey["diskUsedGib"] shouldBe ""
            // free/idle and the repo size have no meaningful utilization ratio
            classesByKey["cpuIdle"] shouldBe ""
            classesByKey["ramFreeGib"] shouldBe ""
            classesByKey["diskFreeGib"] shouldBe ""
            classesByKey["repoSizeGib"] shouldBe ""
        }

        test("the disk total names the binding source: user quota, group quota, or the volume") {
            UiFormats.diskTotal(metricsWithDisk(70.99, diskSource = null)) shouldBe "70.99 GiB"

            UiFormats.diskTotal(
                metricsWithDisk(
                    8.0,
                    diskSource = DiskSource(kind = "group", subject = "mih09", hardLimitGib = 12.0),
                ),
            ) shouldBe "8.00 GiB (group quota mih09, hard limit 12.00 GiB)"

            UiFormats.diskTotal(
                metricsWithDisk(
                    4.0,
                    diskSource = DiskSource(kind = "user", subject = "mih09-werkator", hardLimitGib = 6.0),
                ),
            ) shouldBe "4.00 GiB (user quota mih09-werkator, hard limit 6.00 GiB)"

            UiFormats.diskTotal(
                metricsWithDisk(70.99, diskSource = DiskSource.volume(), quotasPresent = true),
            ) shouldBe "70.99 GiB (volume, tighter than the quotas)"

            UiFormats.diskTotal(
                metricsWithDisk(70.99, diskSource = DiskSource.volume(), quotasPresent = false),
            ) shouldBe "70.99 GiB"
        }

        test("the disk total is n/a when the disk metric itself is unavailable") {
            UiFormats.diskTotal(metricsWithDisk(0.0, diskSource = null).copy(diskTotalGib = null)) shouldBe "n/a"
        }

        test("Gitea web links escape branch segments but keep slashes") {
            val links =
                GiteaWebLinks(GiteaConfig(baseUrl = "https://git.example.org/", owner = "acme", repo = "widget"))

            links.repoUrl shouldBe "https://git.example.org/acme/widget"
            links.branchUrl("feature/some topic") shouldBe
                "https://git.example.org/acme/widget/src/branch/feature/some%20topic"
            links.commitUrl("0123abc") shouldBe "https://git.example.org/acme/widget/commit/0123abc"
        }

        test("Gitea web links are null when Gitea is not configured") {
            val links = GiteaWebLinks(GiteaConfig())

            links.repoUrl shouldBe null
            links.branchUrl("main") shouldBe null
            links.commitUrl("0123abc") shouldBe null
        }

        test("rows are in progress while running or pending, so the artifact link shows the log-only icon") {
            val links = GiteaWebLinks(GiteaConfig())

            fun row(status: String) =
                BuildRowView.from(
                    BranchDto(
                        branch = "main",
                        commit = "0123abc",
                        status = status,
                        startedAt = null,
                        durationSeconds = null,
                        artifactKey = "some-key",
                    ),
                    links,
                )

            row("running").inProgress shouldBe true
            row("pending").inProgress shouldBe true
            row("success").inProgress shouldBe false
            row("failed").inProgress shouldBe false
        }
    }
}
