<!--
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 * as semver from 'semver'
import { computed, markRaw, ref } from 'vue'
import { useRouter } from 'vue-router'

import { Runtime } from '@/api/common/omni.pb'
import type { Resource } from '@/api/grpc'
import type {
  ClusterDiagnosticsSpec,
  ClusterDiagnosticsSpecNode,
  ClusterStatusSpec,
  ControlPlaneStatusSpec,
  EtcdBackupStatusSpec,
  KubernetesStatusSpec,
  KubernetesUpgradeStatusSpec,
  TalosUpgradeStatusSpec,
} from '@/api/omni/specs/omni.pb'
import {
  ClusterDiagnosticsType,
  ClusterLocked,
  ClusterStatusType,
  ControlPlaneStatusType,
  DefaultNamespace,
  EtcdBackupStatusType,
  KubernetesStatusType,
  MetricsNamespace,
} from '@/api/resources'
import TButton from '@/components/Button/TButton.vue'
import TIcon from '@/components/Icon/TIcon.vue'
import ManagedByTemplatesWarning from '@/components/ManagedByTemplatesWarning.vue'
import ClusterDestroy from '@/components/Modals/ClusterDestroy.vue'
import TSpinner from '@/components/Spinner/TSpinner.vue'
import Tooltip from '@/components/Tooltip/Tooltip.vue'
import type { BackupsStatus } from '@/methods'
import { downloadKubeconfig, downloadTalosconfig } from '@/methods'
import { useClusterPermissions } from '@/methods/auth'
import { triggerEtcdBackup, updateClusterLock } from '@/methods/cluster'
import { controlPlaneMachineSetId } from '@/methods/machineset'
import { formatISO } from '@/methods/time'
import { useResourceWatch } from '@/methods/useResourceWatch'
import { showError, showWarning } from '@/notification'
import TClusterStatus from '@/views/Clusters/ClusterStatus.vue'
import DownloadSupportBundleModal from '@/views/Overview/components/OverviewRightPanel/DownloadSupportBundleModal.vue'
import OverviewOIDCToast from '@/views/Overview/components/OverviewRightPanel/OverviewOIDCToast.vue'
import OverviewRightPanelCondition from '@/views/Overview/components/OverviewRightPanel/OverviewRightPanelCondition.vue'
import OverviewRightPanelItem from '@/views/Overview/components/OverviewRightPanel/OverviewRightPanelItem.vue'

const { clusterId, kubernetesUpgradeStatus, talosUpgradeStatus } = defineProps<{
  clusterId: string
  kubernetesUpgradeStatus?: Resource<KubernetesUpgradeStatusSpec>
  talosUpgradeStatus?: Resource<TalosUpgradeStatusSpec>
  etcdBackups?: BackupsStatus
}>()

const router = useRouter()

const { data: clusterStatus } = useResourceWatch<ClusterStatusSpec>(() => ({
  runtime: Runtime.Omni,
  resource: {
    namespace: DefaultNamespace,
    type: ClusterStatusType,
    id: clusterId,
  },
}))

const { data: backupStatus } = useResourceWatch<EtcdBackupStatusSpec>(() => ({
  runtime: Runtime.Omni,
  resource: {
    namespace: MetricsNamespace,
    type: EtcdBackupStatusType,
    id: clusterId,
  },
}))

const { data: controlPlaneStatus } = useResourceWatch<ControlPlaneStatusSpec>(() => ({
  runtime: Runtime.Omni,
  resource: {
    namespace: DefaultNamespace,
    type: ControlPlaneStatusType,
    id: controlPlaneMachineSetId(clusterId),
  },
}))

const { data: kubernetesStatus } = useResourceWatch<KubernetesStatusSpec>(() => ({
  runtime: Runtime.Omni,
  resource: {
    namespace: DefaultNamespace,
    type: KubernetesStatusType,
    id: clusterId,
  },
}))

const { data: clusterDiagnostics } = useResourceWatch<ClusterDiagnosticsSpec>(() => ({
  runtime: Runtime.Omni,
  resource: {
    namespace: DefaultNamespace,
    type: ClusterDiagnosticsType,
    id: clusterId,
  },
}))

const numNodesWithDiagnostics = computed(() => {
  return clusterDiagnostics.value?.spec.nodes?.length || 0
})

const numTotalDiagnostics = computed(() => {
  const nodes: ClusterDiagnosticsSpecNode[] = clusterDiagnostics.value?.spec.nodes || []
  return nodes.reduce((sum, node) => sum + (node.num_diagnostics || 0), 0) || 0
})

enum Update {
  Talos,
  Kubernetes,
}

const lastBackupError = computed(() => {
  return backupStatus.value?.spec.error
})

const backupTime = computed(() => {
  const t = backupStatus.value?.spec.last_backup_time

  if (!t) {
    return 'Never'
  }

  return formatISO(t, 'yyyy-LL-dd HH:mm:ss')
})

const startingEtcdBackup = ref(false)

const runEtcdBackup = async () => {
  startingEtcdBackup.value = true

  try {
    await triggerEtcdBackup(clusterId)
  } catch (e) {
    showError('Failed to Trigger Manual Etcd Backup', e.message)
  }

  startingEtcdBackup.value = false
}

const kubernetesUpgradeAvailable = () => {
  return (kubernetesUpgradeStatus?.spec.upgrade_versions?.length ?? 0) > 0
}

const talosUpdateAvailable = () => {
  return (talosUpgradeStatus?.spec.upgrade_versions?.length ?? 0) > 0
}

const getUpgradeAvailable = (spec: {
  last_upgrade_version?: string
  current_upgrade_version?: string
  upgrade_versions?: string[]
}) => {
  if (spec.current_upgrade_version || !spec.upgrade_versions || !spec.last_upgrade_version) {
    return []
  }

  return spec.upgrade_versions.filter((version: string) => {
    return semver.compare(spec.last_upgrade_version!, version) === -1
  })
}

const kubernetesVersion = computed(() => {
  return getVersion(kubernetesUpgradeStatus!.spec)
})

const talosVersion = computed(() => {
  return getVersion(talosUpgradeStatus!.spec)
})

const newKubernetesVersionsAvailable = computed(() => {
  return getUpgradeAvailable(kubernetesUpgradeStatus!.spec)
})

const newTalosVersionsAvailable = computed(() => {
  return getUpgradeAvailable(talosUpgradeStatus!.spec)
})

const getVersion = (spec: { last_upgrade_version?: string; current_upgrade_version?: string }) => {
  if (spec.current_upgrade_version && spec.last_upgrade_version) {
    return `${spec.last_upgrade_version} ⇾ ${spec.current_upgrade_version}`
  }

  return spec.last_upgrade_version
}

const openClusterUpdate = (type: Update, locked: boolean) => {
  if (locked) {
    return
  }

  if (type === Update.Kubernetes && !canUpdateKubernetes.value) {
    return
  }

  if (type === Update.Talos && !canUpdateTalos.value) {
    return
  }

  const modal = type === Update.Talos ? 'updateTalos' : 'updateKubernetes'

  router.push({ query: { modal: modal, cluster: clusterStatus.value?.metadata.id } })
}

const locked = computed(() => {
  return clusterStatus.value?.metadata.annotations?.[ClusterLocked] !== undefined
})

const updateLock = async () => {
  if (!clusterStatus.value?.metadata.id) {
    return
  }

  try {
    await updateClusterLock(clusterStatus.value.metadata.id, !locked.value)
  } catch (e) {
    showError('Failed To Update Cluster Lock', e.message)
  }
}

const {
  canUpdateKubernetes,
  canUpdateTalos,
  canDownloadKubeconfig,
  canDownloadTalosconfig,
  canDownloadSupportBundle,
  canAddClusterMachines,
  canRemoveClusterMachines,
} = useClusterPermissions(computed(() => clusterId))

const clusterDestroyModalOpen = ref(false)
const downloadSupportBundleModalOpen = ref(false)
</script>

<template>
  <div class="min-w-67 rounded bg-naturals-n2 py-5">
    <div class="flex flex-col gap-4 px-4 lg:px-6">
      <h3 class="text-sm text-naturals-n13">Cluster Details</h3>
      <ManagedByTemplatesWarning warning-style="short" />
      <OverviewRightPanelItem
        v-if="clusterStatus?.metadata.created"
        name="Created"
        :value="formatISO(clusterStatus.metadata.created, 'yyyy-LL-dd HH:mm:ss')"
      />
      <OverviewRightPanelItem v-if="clusterStatus" name="Status">
        <TClusterStatus :cluster="clusterStatus" />
      </OverviewRightPanelItem>
      <OverviewRightPanelItem
        v-if="clusterStatus?.spec.machines?.total"
        name="Machines Healthy"
        :value="`${clusterStatus.spec.machines.healthy ?? 0}/${clusterStatus.spec.machines.total}`"
      />
      <OverviewRightPanelItem v-if="numNodesWithDiagnostics > 0" name="Node Warnings">
        <div class="flex items-center gap-1">
          {{ numTotalDiagnostics }} (in {{ numNodesWithDiagnostics }} nodes)
          <Tooltip
            description="Some machines have diagnostic warnings. See the machines section for details."
          >
            <TIcon class="h-4 w-4 text-yellow-y1" icon="warning" />
          </Tooltip>
        </div>
      </OverviewRightPanelItem>
      <OverviewRightPanelItem v-if="talosUpgradeStatus" name="Talos Version">
        <div>{{ talosVersion }}</div>
        <Tooltip
          v-if="newTalosVersionsAvailable?.length"
          :description="
            locked
              ? `Newer Talos versions are available: ${newTalosVersionsAvailable.join(', ')}. However, Talos updates are disabled when the cluster is locked.`
              : `Newer Talos versions are available: ${newTalosVersionsAvailable.join(', ')}`
          "
        >
          <TIcon
            v-if="newTalosVersionsAvailable"
            class="h-4 w-4 cursor-pointer"
            icon="upgrade-available"
            @click="openClusterUpdate(Update.Talos, locked)"
          />
        </Tooltip>
      </OverviewRightPanelItem>
      <OverviewRightPanelItem v-if="kubernetesUpgradeStatus" name="Kubernetes Version">
        <div>{{ kubernetesVersion }}</div>
        <Tooltip
          v-if="newKubernetesVersionsAvailable?.length"
          :description="
            locked
              ? `Newer Kubernetes versions are available: ${newKubernetesVersionsAvailable.join(', ')}. However, Kubernetes updates are disabled when the cluster is locked.`
              : `Newer Kubernetes versions are available: ${newKubernetesVersionsAvailable.join(', ')}`
          "
        >
          <TIcon
            class="h-4 w-4 cursor-pointer"
            icon="upgrade-available"
            @click="openClusterUpdate(Update.Kubernetes, locked)"
          />
        </Tooltip>
      </OverviewRightPanelItem>
    </div>
    <template v-if="clusterStatus?.spec">
      <div class="my-3 h-px bg-naturals-n4" />
      <div class="flex flex-col gap-4 px-4 lg:px-6">
        <h3 class="text-sm text-naturals-n13">Control Plane</h3>
        <OverviewRightPanelItem name="Ready">
          <span :class="clusterStatus.spec.controlplaneReady ? '' : 'text-red-r1'">
            {{ clusterStatus.spec.controlplaneReady ? 'Yes' : 'No' }}
          </span>
        </OverviewRightPanelItem>
        <OverviewRightPanelItem v-if="clusterStatus.metadata.created" name="Last Backup">
          <div v-if="startingEtcdBackup" class="flex gap-1">
            Starting...
            <TSpinner class="h-4 w-4" />
          </div>
          <template v-else>
            <Tooltip v-if="lastBackupError">
              <template #description>
                <div class="max-w-lg wrap-break-word">{{ lastBackupError }}</div>
              </template>
              <TIcon class="h-4 w-4 text-yellow-y1" icon="warning" />
            </Tooltip>
            {{ backupTime }}
            <Tooltip v-if="etcdBackups?.enabled" description="Trigger Etcd Backup">
              <TIcon
                class="h-4 w-4 cursor-pointer text-green-g1"
                icon="play-circle"
                @click="runEtcdBackup"
              />
            </Tooltip>
          </template>
        </OverviewRightPanelItem>
        <OverviewRightPanelCondition
          v-for="condition in controlPlaneStatus?.spec.conditions"
          :key="condition.type?.toString()"
          :condition="condition"
        />
      </div>
      <div class="my-3 h-px bg-naturals-n4" />
      <div class="flex flex-col gap-4 px-4 lg:px-6">
        <h3 class="text-sm text-naturals-n13">Kubernetes</h3>
        <OverviewRightPanelItem
          name="API Available"
          :value="clusterStatus.spec.kubernetesAPIReady ? 'Yes' : 'No'"
        />
        <OverviewRightPanelItem
          v-if="kubernetesStatus && kubernetesStatus.spec.nodes"
          name="Nodes"
          :value="kubernetesStatus.spec.nodes.length ?? 0"
        />
      </div>
      <div class="my-3 h-px bg-naturals-n4" />
      <div class="flex flex-col gap-4 px-4 lg:px-6">
        <TButton
          :disabled="!canDownloadKubeconfig"
          variant="primary"
          icon="kube-config"
          icon-position="left"
          @click="
            () => {
              downloadKubeconfig(clusterStatus!.metadata.id!)
              showWarning('Note on kubectl', { description: markRaw(OverviewOIDCToast) })
            }
          "
        >
          Download
          <code>kubeconfig</code>
        </TButton>

        <TButton
          :disabled="!canDownloadTalosconfig"
          variant="primary"
          icon="talos-config"
          icon-position="left"
          @click="() => downloadTalosconfig(clusterStatus!.metadata.id!)"
        >
          Download
          <code>talosconfig</code>
        </TButton>

        <TButton
          :disabled="!canDownloadSupportBundle"
          variant="primary"
          icon="lifebuoy"
          icon-position="left"
          aria-haspopup="dialog"
          @click="downloadSupportBundleModalOpen = true"
        >
          Download Support Bundle
        </TButton>

        <TButton
          is="router-link"
          variant="primary"
          icon="kube-config"
          icon-position="left"
          :to="{
            query: { modal: 'exportClusterTemplate' },
          }"
        >
          Export Cluster Template
        </TButton>
      </div>
      <div class="my-3 h-px bg-naturals-n4" />
      <div class="flex flex-col gap-4 px-4 lg:px-6">
        <Tooltip
          class="grow"
          :disabled="!locked"
          description="Cluster scaling is disabled when the cluster is locked."
        >
          <TButton
            is="router-link"
            variant="highlighted"
            icon="nodes"
            icon-position="left"
            :disabled="!canAddClusterMachines || locked"
            :to="{
              name: 'ClusterScale',
              params: { cluster: clusterId },
            }"
          >
            Cluster Scaling
          </TButton>
        </Tooltip>

        <Tooltip
          class="grow"
          :disabled="!locked"
          description="Kubernetes updates are disabled when the cluster is locked."
        >
          <TButton
            variant="primary"
            icon="kubernetes"
            icon-position="left"
            :disabled="!canUpdateKubernetes || !kubernetesUpgradeAvailable() || locked"
            @click="openClusterUpdate(Update.Kubernetes, locked)"
          >
            Update Kubernetes
          </TButton>
        </Tooltip>

        <Tooltip
          class="grow"
          :disabled="!locked"
          description="Talos updates are disabled when the cluster is locked."
        >
          <TButton
            variant="primary"
            icon="sidero-monochrome"
            icon-position="left"
            :disabled="!canUpdateTalos || !talosUpdateAvailable() || locked"
            @click="openClusterUpdate(Update.Talos, locked)"
          >
            Update Talos
          </TButton>
        </Tooltip>

        <TButton
          is="router-link"
          variant="primary"
          icon="settings"
          icon-position="left"
          :to="{
            name: 'ClusterConfigPatches',
            params: { cluster: clusterId },
          }"
        >
          Config Patches
        </TButton>

        <TButton
          variant="primary"
          :icon="locked ? 'locked' : 'unlocked'"
          icon-position="left"
          @click="updateLock"
        >
          {{ locked ? 'Unlock Cluster' : 'Lock Cluster' }}
        </TButton>

        <Tooltip
          class="grow"
          :disabled="!locked"
          description="Cluster deletion is disabled when the cluster is locked."
        >
          <TButton
            class="text-red-r1"
            variant="secondary"
            icon="delete"
            icon-position="left"
            :disabled="!canRemoveClusterMachines || locked"
            aria-haspopup="dialog"
            @click="clusterDestroyModalOpen = true"
          >
            Destroy Cluster
          </TButton>
        </Tooltip>

        <ClusterDestroy
          v-model:open="clusterDestroyModalOpen"
          :cluster-id="clusterStatus.metadata.id!"
        />

        <DownloadSupportBundleModal
          v-model:open="downloadSupportBundleModalOpen"
          :cluster-id="clusterStatus.metadata.id!"
        />
      </div>
    </template>
    <div v-else class="flex items-center justify-center p-4">
      <TSpinner class="h-6 w-6" />
    </div>
  </div>
</template>
