<!--
Copyright (c) 2026 Sidero Labs, Inc.

Use of this software is governed by the Business Source License
included in the LICENSE file.
-->
<script setup lang="ts">
import { RadioGroup, RadioGroupLabel, RadioGroupOption } from '@headlessui/vue'
import * as semver from 'semver'
import { computed, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'

import { Runtime } from '@/api/common/omni.pb'
import { ManagementService } from '@/api/omni/management/management.pb'
import type {
  ClusterSpec,
  KubernetesUpgradeStatusSpec,
  KubernetesVersionSpec,
  TalosVersionSpec,
} from '@/api/omni/specs/omni.pb'
import { withContext } from '@/api/options'
import {
  ClusterType,
  DefaultNamespace,
  KubernetesUpgradeStatusType,
  KubernetesVersionType,
  TalosVersionType,
} from '@/api/resources'
import TButton from '@/components/Button/TButton.vue'
import TCheckbox from '@/components/Checkbox/TCheckbox.vue'
import ManagedByTemplatesWarning from '@/components/ManagedByTemplatesWarning.vue'
import TSpinner from '@/components/Spinner/TSpinner.vue'
import { getDocsLink } from '@/methods'
import { upgradeKubernetes } from '@/methods/cluster'
import { useResourceWatch } from '@/methods/useResourceWatch'
import CloseButton from '@/views/Modals/CloseButton.vue'

const route = useRoute()
const router = useRouter()

const runningPrechecks = ref(false)
const preCheckError = ref('')
const selectedVersion = ref('')

const clusterName = computed(() => ('cluster' in route.params ? route.params.cluster : ''))

const { data: cluster } = useResourceWatch<ClusterSpec>(() => ({
  resource: {
    type: ClusterType,
    namespace: DefaultNamespace,
    id: clusterName.value,
  },
  runtime: Runtime.Omni,
}))

const { data: status } = useResourceWatch<KubernetesUpgradeStatusSpec>(() => ({
  resource: {
    namespace: DefaultNamespace,
    type: KubernetesUpgradeStatusType,
    id: clusterName.value,
  },
  runtime: Runtime.Omni,
}))

const { data: allK8sVersions } = useResourceWatch<KubernetesVersionSpec>({
  resource: {
    namespace: DefaultNamespace,
    type: KubernetesVersionType,
  },
  runtime: Runtime.Omni,
})

const { data: allTalosVersionsUnsorted } = useResourceWatch<TalosVersionSpec>({
  resource: {
    type: TalosVersionType,
    namespace: DefaultNamespace,
  },
  runtime: Runtime.Omni,
})

watch(status, () => {
  if (selectedVersion.value === '') {
    selectedVersion.value = status.value?.spec.last_upgrade_version || ''
  }
})

const supportedK8sVersions = computed(() =>
  (status.value?.spec.upgrade_versions ?? [])
    .concat(status.value?.spec.last_upgrade_version ?? '')
    .filter((v) => !!v)
    .sort(semver.compare),
)

interface VersionGroup {
  clusterSupportsGroup: boolean
  minTalosVersion: string
  upgradeable: boolean
  versions: {
    upgradeable: boolean
    version: string
  }[]
}

const groupedK8sVersions = computed(() => {
  const oldestSupportedVersion = supportedK8sVersions.value[0]
  const clusterTalosVersion = allTalosVersions.value.find(
    (v) => v.spec.version === cluster.value?.spec.talos_version,
  )

  return allK8sVersions.value
    .map((v) => semver.parse(v.spec.version, false, true))
    .sort(semver.compare)
    .filter((v) => semver.gte(v, oldestSupportedVersion))
    .reduce<Record<string, VersionGroup>>((result, parsed) => {
      const { major, minor } = parsed
      const version = parsed.format()

      const majorMinor = `${major}.${minor}`

      result[majorMinor] ||= {
        clusterSupportsGroup:
          clusterTalosVersion?.spec.compatible_kubernetes_versions?.some((v) =>
            semver.satisfies(v, majorMinor),
          ) ?? false,
        minTalosVersion:
          allTalosVersions.value.find((v) =>
            v.spec.compatible_kubernetes_versions?.some((v) => semver.satisfies(v, majorMinor)),
          )?.spec.version ?? 'unknown',
        upgradeable: isVersionUpgradeable(`${major}.${minor}.0`),
        versions: [],
      }

      // Only show individual versions if part of an upgradeable group
      if (result[majorMinor].upgradeable) {
        result[majorMinor].versions.push({
          upgradeable: isVersionUpgradeable(version),
          version,
        })
      }

      return result
    }, {})
})

const allTalosVersions = computed(() =>
  allTalosVersionsUnsorted.value.toSorted((a, b) =>
    semver.compare(a.spec.version!, b.spec.version!),
  ),
)

function isVersionUpgradeable(version: string) {
  return supportedK8sVersions.value.includes(version)
}

const k8sSupportMatrixDocsLink = computed(() =>
  getDocsLink('talos', '/getting-started/support-matrix', {
    talosVersion: cluster.value?.spec.talos_version,
  }),
)

const action = computed(() => {
  if (!status.value) {
    return 'Loading...'
  }

  switch (semver.compare(selectedVersion.value, status.value.spec.last_upgrade_version ?? '')) {
    case 1:
      return 'Upgrade'
    case -1:
      return 'Downgrade'
  }

  return 'Unchanged'
})

let closed = false

const close = () => {
  if (closed) {
    return
  }

  closed = true

  router.go(-1)
}

const upgradeClick = async () => {
  runningPrechecks.value = true
  preCheckError.value = ''

  try {
    const response = await ManagementService.KubernetesUpgradePreChecks(
      {
        new_version: selectedVersion.value,
      },
      withContext({ cluster: clusterName.value }),
    )

    if (response.ok) {
      upgradeKubernetes(clusterName.value, selectedVersion.value)

      close()
    } else {
      preCheckError.value = response.reason!
    }
  } catch (e) {
    preCheckError.value = e.message || e.toString()
  }

  runningPrechecks.value = false
}
</script>

<template>
  <div class="modal-window my-4 flex max-h-screen flex-col gap-2">
    <div class="mb-5 flex items-center justify-between text-xl text-naturals-n14">
      <h3 class="text-base text-naturals-n14">Update Kubernetes</h3>
      <CloseButton @click="close" />
    </div>
    <ManagedByTemplatesWarning warning-style="popup" />
    <template v-if="status">
      <RadioGroup
        id="k8s-upgrade-version"
        v-model="selectedVersion"
        class="flex max-h-64 flex-1 flex-col gap-2 overflow-y-auto text-naturals-n13"
      >
        <template
          v-for="(
            { minTalosVersion, clusterSupportsGroup, upgradeable: groupUpgradeable, versions },
            group
          ) in groupedK8sVersions"
          :key="group"
        >
          <RadioGroupLabel
            as="div"
            class="sticky top-0 w-full bg-naturals-n4 p-1 pl-7 text-sm font-bold"
          >
            {{ group }}
            {{
              groupUpgradeable
                ? ''
                : clusterSupportsGroup
                  ? " - Can't skip minor version upgrades"
                  : ` - Requires Talos version ${minTalosVersion}`
            }}
          </RadioGroupLabel>
          <div class="flex flex-col gap-1">
            <RadioGroupOption
              v-for="{ upgradeable, version } in versions"
              :key="version"
              v-slot="{ checked }"
              :value="version"
              :disabled="!upgradeable"
            >
              <div
                class="tranform transition-color flex cursor-pointer items-center gap-2 px-2 py-1 text-sm hover:bg-naturals-n4"
                :class="{ 'bg-naturals-n4': checked }"
              >
                <TCheckbox
                  :model-value="checked"
                  class="pointer-events-none"
                  :disabled="!upgradeable"
                  @vue:mounted="
                    ($event) =>
                      checked && ($event.el as HTMLElement).scrollIntoView({ block: 'center' })
                  "
                />
                {{ version }}
                <span v-if="version === status.spec.last_upgrade_version">(current)</span>
              </div>
            </RadioGroupOption>
          </div>
        </template>
      </RadioGroup>
    </template>

    <p class="text-xs">
      Downgrading minor versions is not supported. You can not skip minor version upgrades. You can
      only upgrade to versions supported by your Talos version. See the
      <a class="link-primary" :href="k8sSupportMatrixDocsLink">support matrix</a>
      for which versions are supported by your Talos version.
    </p>

    <p class="text-xs">
      Changing the Kubernetes version can result in control plane downtime. During this change you
      will be able to cancel the upgrade.
    </p>
    <p class="text-xs">This operation starts immediately.</p>

    <div v-if="runningPrechecks" class="text-xs text-primary-p3">
      Running pre-checks to validate the upgrade...
    </div>

    <div v-if="preCheckError" class="font-mono text-xs whitespace-pre-line text-primary-p3">
      {{ preCheckError }}
    </div>

    <div class="flex justify-end gap-4">
      <TButton
        class="h-9 w-32"
        :disabled="
          !status || runningPrechecks || selectedVersion === status?.spec?.last_upgrade_version
        "
        variant="highlighted"
        @click="upgradeClick"
      >
        <TSpinner v-if="runningPrechecks || !status" class="h-5 w-5" />
        <span v-else>{{ action }}</span>
      </TButton>
    </div>
  </div>
</template>
