// frontend\src\pages\administration\milestone\MilestoneAdministration.tsx

import { useEffect, useRef, useState, type FormEvent } from 'react'
import {
  KeyIcon,
  ServerStackIcon,
  VideoCameraIcon,
} from '@heroicons/react/20/solid'
import Button from '../../../components/Button'
import LoadingSpinner from '../../../components/LoadingSpinner'
import { useNotifications } from '../../../components/Notifications'
import Switch from '../../../components/Switch'
import { DialogTitle } from '@headlessui/react'
import { XMarkIcon } from '@heroicons/react/24/outline'
import Modal from '../../../components/Modal'
import TaskList, { type TaskListStep } from '../../../components/TaskList'
import TreeList, { type TreeListNode } from '../../../components/TreeList'

const API_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:8080'

type MilestoneSettings = {
  host: string
  username: string
  passwordConfigured: boolean
  skipTlsVerify: boolean
  tokenConfigured: boolean
  tokenType: string
  tokenExpiresAt?: string
  tokenUpdatedAt?: string
  serverId: string
  serverName: string
  serverVersion: string
  serverUpdatedAt?: string
  serverFoldersAgentScheme: string
  serverFoldersAgentPort: string
  serverFoldersAgentTokenConfigured: boolean
  serverFoldersStorageRootLabel: string
  serverFoldersStorageRootPath: string
  serverFoldersArchiveRootLabel: string
  serverFoldersArchiveRootPath: string
  backupNasHost: string
  backupNasShare: string
  backupNasUsername: string
  backupNasPasswordConfigured: boolean
  backupLogRootPath: string
  updatedAt?: string
}

type BackupFolderRoot = {
  label: string
  path: string
}

type BackupFolderItem = {
  name: string
  displayName?: string
  path: string
  hasChildren?: boolean
}

type BackupFoldersResponse = {
  path: string
  roots?: BackupFolderRoot[]
  folders?: BackupFolderItem[]
  warning?: string
}
type MilestoneSyncEvent = {
  type: 'token' | 'recordingServer' | 'hardware' | 'done' | 'error'
  message?: string
  tokenType?: string
  tokenExpiresAt?: string
  recordingServerId?: string
  recordingServerName?: string
  recordingServerVersion?: string
  hardwareCount?: number
  settings?: MilestoneSettings
}

const inputClassName =
  'block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500'

async function readApiError(response: Response, fallback: string) {
  const data = await response.json().catch(() => null)

  return data?.error ?? fallback
}

function formatUpdatedAt(value?: string) {
  if (!value) {
    return null
  }

  try {
    return new Intl.DateTimeFormat('de-DE', {
      dateStyle: 'short',
      timeStyle: 'short',
    }).format(new Date(value))
  } catch {
    return value
  }
}

function createMilestoneSyncSteps(): TaskListStep[] {
  return [
    {
      id: 'token',
      name: 'Token abrufen',
      description: 'Authentifizierung am Milestone-Server läuft.',
      status: 'current',
    },
    {
      id: 'recordingServer',
      name: 'Recording-Server abfragen',
      description: 'Server-ID, Name und Version werden geladen.',
      status: 'upcoming',
    },
    {
      id: 'hardware',
      name: 'Milestone-Daten synchronisieren',
      description: 'Geräte, Untergeräte und Treiberdaten werden geladen.',
      status: 'upcoming',
    },
  ]
}

function updateMilestoneSyncStepsForEvent(
  steps: TaskListStep[],
  event: MilestoneSyncEvent,
): TaskListStep[] {
  if (event.type === 'token') {
    const tokenExpiresAt = formatUpdatedAt(event.tokenExpiresAt)

    return steps.map((step) => {
      if (step.id === 'token') {
        return {
          ...step,
          status: 'complete',
          description: tokenExpiresAt
            ? `Token wurde abgerufen. Gültig bis ${tokenExpiresAt}.`
            : 'Token wurde erfolgreich abgerufen.',
        }
      }

      if (step.id === 'recordingServer') {
        return {
          ...step,
          status: 'current',
        }
      }

      return step
    })
  }

  if (event.type === 'recordingServer') {
    const recordingServerDescription = [
      event.recordingServerName || 'Recording-Server wurde abgerufen.',
      event.recordingServerVersion ? `Version ${event.recordingServerVersion}` : '',
      event.recordingServerId ? `ID: ${event.recordingServerId}` : '',
    ]
      .filter(Boolean)
      .join(' · ')

    return steps.map((step) => {
      if (step.id === 'recordingServer') {
        return {
          ...step,
          status: 'complete',
          description: recordingServerDescription,
        }
      }

      if (step.id === 'hardware') {
        return {
          ...step,
          status: 'current',
        }
      }

      return step
    })
  }

  if (event.type === 'hardware') {
    return steps.map((step) => {
      if (step.id === 'hardware') {
        return {
          ...step,
          status: 'complete',
          description:
            event.hardwareCount === 1
              ? '1 Hardware-Gerät wurde synchronisiert.'
              : `${event.hardwareCount ?? 0} Hardware-Geräte wurden synchronisiert.`,
        }
      }

      return step
    })
  }

  return steps
}

function markCurrentMilestoneSyncStepAsError(
  steps: TaskListStep[],
  message: string,
): TaskListStep[] {
  const currentStepIndex = steps.findIndex((step) => step.status === 'current')
  const upcomingStepIndex = steps.findIndex((step) => step.status === 'upcoming')

  const failedStepIndex =
    currentStepIndex >= 0
      ? currentStepIndex
      : upcomingStepIndex >= 0
        ? upcomingStepIndex
        : steps.length - 1

  return steps.map((step, index) =>
    index === failedStepIndex
      ? {
          ...step,
          status: 'error',
          description: message,
        }
      : step,
  )
}

export default function MilestoneAdministration() {
  const { showToast, showErrorToast } = useNotifications()
  const [settings, setSettings] = useState<MilestoneSettings | null>(null)
  const [isLoading, setIsLoading] = useState(true)
  const [isSaving, setIsSaving] = useState(false)
  const [isFetchingToken, setIsFetchingToken] = useState(false)
  const [skipTlsVerify, setSkipTlsVerify] = useState(false)
  const [syncModalOpen, setSyncModalOpen] = useState(false)
  const [syncSteps, setSyncSteps] = useState<TaskListStep[]>(() => createMilestoneSyncSteps())
  const [syncError, setSyncError] = useState<string | null>(null)
  const [syncCompleted, setSyncCompleted] = useState(false)
  const [backupNasHost, setBackupNasHost] = useState('')
  const [backupNasShare, setBackupNasShare] = useState('')
  const [backupNasUsername, setBackupNasUsername] = useState('')
  const [backupNasPassword, setBackupNasPassword] = useState('')
  const [backupLogRootPath, setBackupLogRootPath] = useState('')

  useEffect(() => {
    void loadSettings()
  }, [])

  function getMilestoneDataCountMessage(count: number) {
    if (count === 1) {
      return '1 Gerät wurde von Milestone geladen und gespeichert.'
    }

    return `${count} Geräte wurden von Milestone geladen und gespeichert.`
  }

  function handleMilestoneSyncEvent(event: MilestoneSyncEvent) {
    setSyncSteps((currentSteps) =>
      updateMilestoneSyncStepsForEvent(currentSteps, event),
    )

    if (event.type === 'error') {
      const message = event.message || 'Milestone-Synchronisierung fehlgeschlagen'

      setSyncError(message)
      setSyncSteps((currentSteps) =>
        markCurrentMilestoneSyncStepAsError(currentSteps, message),
      )

      throw new Error(message)
    }

    if (event.type === 'token') {
      const tokenExpiresAt = formatUpdatedAt(event.tokenExpiresAt)

      showToast({
        variant: 'success',
        title: 'Token abgerufen',
        message: tokenExpiresAt
          ? `Token wurde erfolgreich abgerufen. Gültig bis ${tokenExpiresAt}.`
          : 'Token wurde erfolgreich abgerufen.',
      })

      return
    }

    if (event.type === 'recordingServer') {
      showToast({
        variant: 'success',
        title: 'Recording-Infos abgerufen',
        message: [
          event.recordingServerName || 'Recording-Server',
          event.recordingServerVersion
            ? `Version ${event.recordingServerVersion}`
            : '',
          event.recordingServerId
            ? `ID: ${event.recordingServerId}`
            : '',
        ]
          .filter(Boolean)
          .join(' · '),
      })

      return
    }

    if (event.type === 'hardware') {
      showToast({
        variant: 'success',
        title: 'Milestone-Daten synchronisiert',
        message: getMilestoneDataCountMessage(event.hardwareCount ?? 0),
      })

      return
    }

    if (event.type === 'done') {
      setSyncCompleted(true)

      if (event.settings) {
        setSettings(event.settings)
        setSkipTlsVerify(Boolean(event.settings.skipTlsVerify))
        setBackupNasHost(event.settings.backupNasHost ?? '')
        setBackupNasShare(event.settings.backupNasShare ?? '')
        setBackupNasUsername(event.settings.backupNasUsername ?? '')
        setBackupNasPassword('')
        setBackupLogRootPath(event.settings.backupLogRootPath ?? '')
      }

      return
    }
  }

  async function runMilestoneSyncStream() {
    setSyncModalOpen(true)
    setSyncCompleted(false)
    setSyncError(null)
    setSyncSteps(createMilestoneSyncSteps())

    const response = await fetch(`${API_URL}/admin/milestone/token/stream`, {
      method: 'POST',
      credentials: 'include',
    })

    if (!response.ok || !response.body) {
      const message = await readApiError(
        response,
        'Milestone-Token konnte nicht abgerufen werden',
      )

      setSyncError(message)
      setSyncSteps((currentSteps) =>
        markCurrentMilestoneSyncStepAsError(currentSteps, message),
      )

      throw new Error(message)
    }

    const reader = response.body.getReader()
    const decoder = new TextDecoder()

    let buffer = ''

    while (true) {
      const { value, done } = await reader.read()

      if (done) {
        break
      }

      buffer += decoder.decode(value, { stream: true })

      const lines = buffer.split('\n')
      buffer = lines.pop() ?? ''

      for (const line of lines) {
        const trimmedLine = line.trim()

        if (!trimmedLine) {
          continue
        }

        const event = JSON.parse(trimmedLine) as MilestoneSyncEvent
        handleMilestoneSyncEvent(event)
      }
    }

    const remainingLine = buffer.trim()

    if (remainingLine) {
      const event = JSON.parse(remainingLine) as MilestoneSyncEvent
      handleMilestoneSyncEvent(event)
    }
  }

  async function loadSettings() {
    setIsLoading(true)

    try {
      const response = await fetch(`${API_URL}/admin/milestone`, {
        credentials: 'include',
      })

      if (!response.ok) {
        throw new Error(
          await readApiError(
            response,
            'Milestone-Einstellungen konnten nicht geladen werden',
          ),
        )
      }

      const data = await response.json()
      setSettings(data.settings)
      setSkipTlsVerify(Boolean(data.settings?.skipTlsVerify))
      setBackupNasHost(String(data.settings?.backupNasHost ?? ''))
      setBackupNasShare(String(data.settings?.backupNasShare ?? ''))
      setBackupNasUsername(String(data.settings?.backupNasUsername ?? ''))
      setBackupNasPassword('')
      setBackupLogRootPath(String(data.settings?.backupLogRootPath ?? ''))
    } catch (error) {
      showErrorToast({
        title: 'Einstellungen konnten nicht geladen werden',
        error,
        fallback: 'Milestone-Einstellungen konnten nicht geladen werden',
      })
    } finally {
      setIsLoading(false)
    }
  }

  async function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()

    const form = event.currentTarget
    const formData = new FormData(form)

    setIsSaving(true)

    try {
      const response = await fetch(`${API_URL}/admin/milestone?sync=false`, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json',
        },
        credentials: 'include',
        body: JSON.stringify({
          host: String(formData.get('host') ?? ''),
          username: String(formData.get('username') ?? ''),
          password: String(formData.get('password') ?? ''),
          skipTlsVerify,
          serverFoldersAgentScheme: String(
            formData.get('serverFoldersAgentScheme') ?? 'http',
          ),
          serverFoldersAgentPort: String(
            formData.get('serverFoldersAgentPort') ?? '8099',
          ),
          serverFoldersStorageRootLabel: String(
            formData.get('serverFoldersStorageRootLabel') ?? 'Storage D',
          ),
          serverFoldersStorageRootPath: String(
            formData.get('serverFoldersStorageRootPath') ?? 'D:/Milestone-Speicher',
          ),
          serverFoldersArchiveRootLabel: String(
            formData.get('serverFoldersArchiveRootLabel') ?? 'Archive E',
          ),
          serverFoldersArchiveRootPath: String(
            formData.get('serverFoldersArchiveRootPath') ?? 'E:/',
          ),
          backupNasHost,
          backupNasShare,
          backupNasUsername,
          backupNasPassword,
          backupLogRootPath,
        }),
      })

      if (!response.ok) {
        throw new Error(
          await readApiError(
            response,
            'Milestone-Einstellungen konnten nicht gespeichert werden',
          ),
        )
      }

      const data = (await response.json()) as {
        settings: MilestoneSettings
      }

      setSettings(data.settings)
      setSkipTlsVerify(Boolean(data.settings?.skipTlsVerify))
      setBackupNasHost(data.settings.backupNasHost ?? '')
      setBackupNasShare(data.settings.backupNasShare ?? '')
      setBackupNasUsername(data.settings.backupNasUsername ?? '')
      setBackupNasPassword('')
      setBackupLogRootPath(data.settings.backupLogRootPath ?? '')
      const passwordInput = form.elements.namedItem('password')
      if (passwordInput instanceof HTMLInputElement) {
        passwordInput.value = ''
      }

      showToast({
        variant: 'success',
        title: 'Einstellungen gespeichert',
        message: 'Die Milestone-Einstellungen wurden gespeichert.',
      })
    } catch (error) {
      showErrorToast({
        title: 'Speichern fehlgeschlagen',
        error,
        fallback: 'Milestone-Einstellungen konnten nicht gespeichert werden',
      })
    } finally {
      setIsSaving(false)
    }
  }

  async function handleFetchToken() {
    setIsFetchingToken(true)

    try {
      await runMilestoneSyncStream()
    } catch (error) {
      showErrorToast({
        title: 'Abruf fehlgeschlagen',
        error,
        fallback: 'Milestone-Token konnte nicht abgerufen werden',
      })
    } finally {
      setIsFetchingToken(false)
    }
  }

  if (isLoading) {
    return (
      <div className="rounded-lg bg-white p-8 shadow-sm ring-1 ring-gray-200 dark:bg-gray-900 dark:ring-white/10">
        <LoadingSpinner
          size="md"
          label="Milestone-Einstellungen werden geladen..."
          className="text-indigo-600 dark:text-indigo-400"
          center
        />
      </div>
    )
  }

  const updatedAt = formatUpdatedAt(settings?.updatedAt)

  return (
    <>
      <Modal
        open={syncModalOpen}
        setOpen={(open) => {
          const syncIsRunning =
            !syncCompleted && !syncError && (isSaving || isFetchingToken)

          if (!open && syncIsRunning) {
            return
          }

          setSyncModalOpen(open)
        }}
        zIndexClassName="z-[9999]"
        panelClassName="max-w-xl"
      >
        <div className="flex items-start justify-between border-b border-gray-200 px-6 py-4 dark:border-white/10">
          <div>
            <DialogTitle className="text-base font-semibold text-gray-900 dark:text-white">
              Milestone-Synchronisierung
            </DialogTitle>

            <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
              Token, Recording-Server und Milestone-Daten werden nacheinander abgefragt.
            </p>
          </div>

          <button
            type="button"
            disabled={!syncCompleted && !syncError && (isSaving || isFetchingToken)}
            onClick={() => setSyncModalOpen(false)}
            className="rounded-md text-gray-400 hover:text-gray-500 disabled:cursor-not-allowed disabled:opacity-50 dark:hover:text-gray-300"
          >
            <span className="sr-only">Schließen</span>
            <XMarkIcon aria-hidden="true" className="size-6" />
          </button>
        </div>

        <div className="px-6 py-5">
          <TaskList
            steps={syncSteps}
            ariaLabel="Milestone-Synchronisierung"
          />
        </div>

        <div className="flex justify-end border-t border-gray-200 px-6 py-4 dark:border-white/10">
          <Button
            type="button"
            variant="secondary"
            color="gray"
            disabled={!syncCompleted && !syncError && (isSaving || isFetchingToken)}
            onClick={() => setSyncModalOpen(false)}
          >
            {syncCompleted || syncError ? 'Schließen' : 'Bitte warten...'}
          </Button>
        </div>
      </Modal>

      <div className="grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1fr)_28rem]">
        <form
          onSubmit={handleSubmit}
          className="space-y-6"
        >
          <section className="rounded-lg bg-white p-5 shadow-sm ring-1 ring-gray-200 dark:bg-gray-900 dark:ring-white/10">
            <div className="flex items-start gap-x-3 border-b border-gray-200 pb-5 dark:border-white/10">
              <div className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-indigo-50 text-indigo-600 ring-1 ring-indigo-600/10 dark:bg-indigo-500/10 dark:text-indigo-300 dark:ring-indigo-400/20">
                <VideoCameraIcon aria-hidden="true" className="size-5" />
              </div>

              <div>
                <h2 className="text-sm font-semibold text-gray-900 dark:text-white">
                  Milestone Videoserver
                </h2>

                <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
                  Hinterlege die Zugangsdaten für den Milestone Videoserver.
                </p>
              </div>
            </div>

            <div className="mt-5 grid grid-cols-1 gap-5 lg:grid-cols-6">
              <div className="lg:col-span-2">
              <label
                htmlFor="milestone-host"
                className="block text-sm/6 font-medium text-gray-900 dark:text-white"
              >
                IP-Adresse / Hostname *
              </label>

              <div className="mt-2">
                <input
                  id="milestone-host"
                  name="host"
                  type="text"
                  required
                  defaultValue={settings?.host ?? ''}
                  placeholder="z. B. 192.168.178.50"
                  className={inputClassName}
                />
              </div>
            </div>

              <div className="lg:col-span-2">
              <label
                htmlFor="milestone-username"
                className="block text-sm/6 font-medium text-gray-900 dark:text-white"
              >
                Benutzername *
              </label>

              <div className="mt-2">
                <input
                  id="milestone-username"
                  name="username"
                  type="text"
                  required
                  defaultValue={settings?.username ?? ''}
                  autoComplete="off"
                  className={inputClassName}
                />
              </div>
            </div>

              <div className="lg:col-span-2">
              <label
                htmlFor="milestone-password"
                className="block text-sm/6 font-medium text-gray-900 dark:text-white"
              >
                Kennwort {settings?.passwordConfigured ? '(leer lassen = unverändert)' : '*'}
              </label>

              <div className="mt-2">
                <input
                  id="milestone-password"
                  name="password"
                  type="password"
                  required={!settings?.passwordConfigured}
                  autoComplete="new-password"
                  placeholder={
                    settings?.passwordConfigured
                      ? 'Kennwort ist bereits gespeichert'
                      : ''
                  }
                  className={inputClassName}
                />
              </div>
            </div>

              <div className="lg:col-span-6">
              <div className="rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-white/10 dark:bg-white/5">
                <Switch
                  checked={skipTlsVerify}
                  onChange={(event) => setSkipTlsVerify(event.target.checked)}
                  label="TLS-Zertifikatsprüfung deaktivieren"
                  description={
                    <>
                      Nur für selbstsignierte Zertifikate oder interne Testsysteme verwenden.
                    </>
                  }
                  variant="simple"
                />
              </div>
            </div>
            </div>
          </section>

          <section className="rounded-lg bg-white p-5 shadow-sm ring-1 ring-gray-200 dark:bg-gray-900 dark:ring-white/10">
              <div>
                <div className="flex items-start gap-x-3 border-b border-gray-200 pb-5 dark:border-white/10">
                  <div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-indigo-50 text-indigo-600 ring-1 ring-indigo-600/10 dark:bg-indigo-500/10 dark:text-indigo-300 dark:ring-indigo-400/20">
                    <ServerStackIcon aria-hidden="true" className="size-5" />
                  </div>

                  <div>
                    <h3 className="text-sm font-semibold text-gray-900 dark:text-white">
                      TreeView-Agent
                    </h3>
                    <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
                      Verbindung zum Server-Folders-Agent auf dem Milestone-/Recording-Server.
                    </p>
                  </div>
                </div>

                <div className="mt-5 grid grid-cols-1 gap-4 sm:grid-cols-3">
                  <div>
                    <label
                      htmlFor="server-folders-agent-scheme"
                      className="block text-sm/6 font-medium text-gray-900 dark:text-white"
                    >
                      Schema
                    </label>

                    <div className="mt-2">
                      <select
                        id="server-folders-agent-scheme"
                        name="serverFoldersAgentScheme"
                        defaultValue={settings?.serverFoldersAgentScheme || 'http'}
                        className={inputClassName}
                      >
                        <option value="http">http</option>
                        <option value="https">https</option>
                      </select>
                    </div>
                  </div>

                  <div>
                    <label
                      htmlFor="server-folders-agent-port"
                      className="block text-sm/6 font-medium text-gray-900 dark:text-white"
                    >
                      Port
                    </label>

                    <div className="mt-2">
                      <input
                        id="server-folders-agent-port"
                        name="serverFoldersAgentPort"
                        type="text"
                        inputMode="numeric"
                        defaultValue={settings?.serverFoldersAgentPort || '8099'}
                        placeholder="8099"
                        className={inputClassName}
                      />
                    </div>
                  </div>

                  <div>
                    <label
                      htmlFor="server-folders-storage-root-label"
                      className="block text-sm/6 font-medium text-gray-900 dark:text-white"
                    >
                      Storage-Label
                    </label>

                    <div className="mt-2">
                      <input
                        id="server-folders-storage-root-label"
                        name="serverFoldersStorageRootLabel"
                        type="text"
                        defaultValue={settings?.serverFoldersStorageRootLabel || 'Storage D'}
                        placeholder="Storage D"
                        className={inputClassName}
                      />
                    </div>
                  </div>

                  <div>
                    <label
                      htmlFor="server-folders-storage-root-path"
                      className="block text-sm/6 font-medium text-gray-900 dark:text-white"
                    >
                      Storage-Pfad
                    </label>

                    <div className="mt-2">
                      <input
                        id="server-folders-storage-root-path"
                        name="serverFoldersStorageRootPath"
                        type="text"
                        defaultValue={settings?.serverFoldersStorageRootPath || 'D:/Milestone-Speicher'}
                        placeholder="D:/Milestone-Speicher"
                        className={inputClassName}
                      />
                    </div>
                  </div>

                  <div>
                    <label
                      htmlFor="server-folders-archive-root-label"
                      className="block text-sm/6 font-medium text-gray-900 dark:text-white"
                    >
                      Archiv-Label
                    </label>

                    <div className="mt-2">
                      <input
                        id="server-folders-archive-root-label"
                        name="serverFoldersArchiveRootLabel"
                        type="text"
                        defaultValue={settings?.serverFoldersArchiveRootLabel || 'Archive E'}
                        placeholder="Archive E"
                        className={inputClassName}
                      />
                    </div>
                  </div>

                  <div>
                    <label
                      htmlFor="server-folders-archive-root-path"
                      className="block text-sm/6 font-medium text-gray-900 dark:text-white"
                    >
                      Archiv-Pfad
                    </label>

                    <div className="mt-2">
                      <input
                        id="server-folders-archive-root-path"
                        name="serverFoldersArchiveRootPath"
                        type="text"
                        defaultValue={settings?.serverFoldersArchiveRootPath || 'E:/'}
                        placeholder="E:/"
                        className={inputClassName}
                      />
                    </div>
                  </div>

                  <div className="sm:col-span-3">
                    <p className="rounded-md bg-gray-50 p-3 text-xs text-gray-500 ring-1 ring-gray-200 dark:bg-white/5 dark:text-gray-400 dark:ring-white/10">
                      Der Agent-Token wird beim Speichern automatisch erzeugt, an
                      den Agent übertragen und verschlüsselt gespeichert. Der Agent
                      wird daher ohne Token gestartet – eine manuelle Eingabe ist
                      nicht mehr nötig.{' '}
                      {settings?.serverFoldersAgentTokenConfigured
                        ? 'Aktuell ist ein Token gesetzt.'
                        : 'Aktuell ist noch kein Token gesetzt.'}
                    </p>
                  </div>
                </div>
              </div>
          </section>

          <section className="rounded-lg bg-white p-5 shadow-sm ring-1 ring-gray-200 dark:bg-gray-900 dark:ring-white/10">
              <div>
                <div className="flex items-start gap-x-3 border-b border-gray-200 pb-5 dark:border-white/10">
                  <div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-indigo-50 text-indigo-600 ring-1 ring-indigo-600/10 dark:bg-indigo-500/10 dark:text-indigo-300 dark:ring-indigo-400/20">
                    <ServerStackIcon aria-hidden="true" className="size-5" />
                  </div>

                  <div className="min-w-0 flex-1">
                    <h3 className="text-sm font-semibold text-gray-900 dark:text-white">
                      Backup-Protokolle
                    </h3>
                    <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
                      NAS-Zugang und Ordner mit den Robocopy-.log-Dateien.
                    </p>
                  </div>
                </div>

                <div className="mt-5 grid grid-cols-1 gap-4 sm:grid-cols-3">
                  <div>
                    <label
                      htmlFor="backup-nas-host"
                      className="block text-sm/6 font-medium text-gray-900 dark:text-white"
                    >
                      NAS-IP / Hostname
                    </label>

                    <div className="mt-2">
                      <input
                        id="backup-nas-host"
                        type="text"
                        value={backupNasHost}
                        onChange={(event) => {
                          setBackupNasHost(event.target.value)
                          setBackupNasShare('')
                          setBackupLogRootPath('')
                        }}
                        placeholder="z. B. 192.168.178.20 oder NAS01"
                        className={inputClassName}
                      />
                    </div>
                  </div>

                  <div>
                    <label
                      htmlFor="backup-nas-username"
                      className="block text-sm/6 font-medium text-gray-900 dark:text-white"
                    >
                      Benutzername
                    </label>

                    <div className="mt-2">
                      <input
                        id="backup-nas-username"
                        type="text"
                        value={backupNasUsername}
                        onChange={(event) => setBackupNasUsername(event.target.value)}
                        placeholder="z. B. backup-user oder DOMÄNE\\backup-user"
                        autoComplete="off"
                        className={inputClassName}
                      />
                    </div>
                  </div>

                  <div>
                    <label
                      htmlFor="backup-nas-password"
                      className="block text-sm/6 font-medium text-gray-900 dark:text-white"
                    >
                      Kennwort {settings?.backupNasPasswordConfigured ? '(leer lassen = unverändert)' : ''}
                    </label>

                    <div className="mt-2">
                      <input
                        id="backup-nas-password"
                        type="password"
                        value={backupNasPassword}
                        onChange={(event) => setBackupNasPassword(event.target.value)}
                        autoComplete="new-password"
                        placeholder={
                          settings?.backupNasPasswordConfigured
                            ? 'Gespeichertes Kennwort verwenden'
                            : ''
                        }
                        className={inputClassName}
                      />
                    </div>
                  </div>
                </div>

                <div className="mt-6 border-t border-gray-200 pt-4 dark:border-white/10">
                  <div>
                    <BackupFolderPicker
                      value={backupLogRootPath}
                      nasHost={backupNasHost}
                      nasShare={backupNasShare}
                      nasUsername={backupNasUsername}
                      nasPassword={backupNasPassword}
                      storedPasswordAvailable={Boolean(
                        settings?.backupNasPasswordConfigured &&
                          backupNasHost.trim() === (settings.backupNasHost ?? '').trim() &&
                          backupNasUsername.trim() === (settings.backupNasUsername ?? '').trim(),
                      )}
                      onChange={setBackupLogRootPath}
                      onShareChange={setBackupNasShare}
                      disabled={isSaving}
                    />
                  </div>
                </div>
              </div>
          </section>

          <div className="flex flex-col gap-3 rounded-lg bg-white p-4 shadow-sm ring-1 ring-gray-200 sm:flex-row sm:items-center sm:justify-between dark:bg-gray-900 dark:ring-white/10">
            <p className="text-sm text-gray-500 dark:text-gray-400">
              Speichern aktualisiert nur die Einstellungen. Der Milestone-Abruf
              läuft separat.
            </p>

            <div className="flex justify-end gap-x-3">
              <Button
                type="button"
                variant="secondary"
                color="indigo"
                isLoading={isFetchingToken}
                disabled={!settings?.passwordConfigured || isSaving}
                onClick={() => void handleFetchToken()}
              >
                Milestone synchronisieren
              </Button>

              <Button
                type="submit"
                color="indigo"
                isLoading={isSaving}
              >
                Speichern
              </Button>
            </div>
          </div>
        </form>

        <aside className="rounded-lg bg-white p-5 shadow-sm ring-1 ring-gray-200 xl:sticky xl:top-6 xl:self-start dark:bg-gray-900 dark:ring-white/10">
          <div className="flex items-start gap-x-3 border-b border-gray-200 pb-5 dark:border-white/10">
            <div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-gray-50 text-gray-500 ring-1 ring-gray-200 dark:bg-white/5 dark:text-gray-400 dark:ring-white/10">
              <ServerStackIcon aria-hidden="true" className="size-5" />
            </div>

            <div>
              <h3 className="text-sm font-semibold text-gray-900 dark:text-white">
                Status
              </h3>

              <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
                Aktuell gespeicherte Milestone-Konfiguration.
              </p>
            </div>
          </div>

          <dl className="mt-5 grid grid-cols-1 gap-x-6 gap-y-4 text-sm sm:grid-cols-2">
            <div className="min-w-0">
              <dt className="text-gray-500 dark:text-gray-400">
                Server
              </dt>
              <dd className="mt-1 break-all font-medium text-gray-900 dark:text-white">
                {settings?.host || 'Nicht gespeichert'}
              </dd>
            </div>

            <div className="min-w-0">
              <dt className="text-gray-500 dark:text-gray-400">
                Recording-Server-ID
              </dt>
              <dd className="mt-1 break-all font-medium text-gray-900 dark:text-white">
                {settings?.serverId || 'Nicht gespeichert'}
              </dd>
            </div>

            <div className="min-w-0">
              <dt className="text-gray-500 dark:text-gray-400">
                Benutzername
              </dt>
              <dd className="mt-1 break-all font-medium text-gray-900 dark:text-white">
                {settings?.username || 'Nicht gespeichert'}
              </dd>
            </div>

            <div>
              <dt className="text-gray-500 dark:text-gray-400">
                Kennwort
              </dt>
              <dd className="mt-1 flex items-center gap-x-2 font-medium text-gray-900 dark:text-white">
                <KeyIcon aria-hidden="true" className="size-4 text-gray-400" />
                {settings?.passwordConfigured ? 'Gespeichert' : 'Nicht gespeichert'}
              </dd>
            </div>

            <div>
              <dt className="text-gray-500 dark:text-gray-400">
                TLS-Zertifikatsprüfung
              </dt>
              <dd className="mt-1 font-medium text-gray-900 dark:text-white">
                {settings?.skipTlsVerify ? 'Deaktiviert' : 'Aktiv'}
              </dd>
            </div>

            <div>
              <dt className="text-gray-500 dark:text-gray-400">
                Auth-Token
              </dt>
              <dd className="mt-1 font-medium text-gray-900 dark:text-white">
                {settings?.tokenConfigured ? 'Gespeichert' : 'Nicht gespeichert'}
              </dd>
            </div>

            <div>
              <dt className="text-gray-500 dark:text-gray-400">
                TreeView-Agent
              </dt>
              <dd className="mt-1 font-medium text-gray-900 dark:text-white">
                {(settings?.serverFoldersAgentScheme || 'http') +
                  '://' +
                  (settings?.host || 'Milestone-Server') +
                  ':' +
                  (settings?.serverFoldersAgentPort || '8099')}
              </dd>
            </div>

            <div>
              <dt className="text-gray-500 dark:text-gray-400">
                TreeView-Agent Token
              </dt>
              <dd className="mt-1 flex items-center gap-x-2 font-medium text-gray-900 dark:text-white">
                <KeyIcon aria-hidden="true" className="size-4 text-gray-400" />
                {settings?.serverFoldersAgentTokenConfigured
                  ? 'Gespeichert'
                  : 'Nicht gespeichert'}
              </dd>
            </div>

            <div className="min-w-0">
              <dt className="text-gray-500 dark:text-gray-400">
                Storage-Root
              </dt>
              <dd className="mt-1 break-all font-medium text-gray-900 dark:text-white">
                {(settings?.serverFoldersStorageRootLabel || 'Storage D') +
                  ': ' +
                  (settings?.serverFoldersStorageRootPath || 'D:/Milestone-Speicher')}
              </dd>
            </div>

            <div className="min-w-0">
              <dt className="text-gray-500 dark:text-gray-400">
                Archiv-Root
              </dt>
              <dd className="mt-1 break-all font-medium text-gray-900 dark:text-white">
                {(settings?.serverFoldersArchiveRootLabel || 'Archive E') +
                  ': ' +
                  (settings?.serverFoldersArchiveRootPath || 'E:/')}
              </dd>
            </div>

            <div className="min-w-0">
              <dt className="text-gray-500 dark:text-gray-400">
                Backup-NAS
              </dt>
              <dd className="mt-1 break-all font-medium text-gray-900 dark:text-white">
                {settings?.backupNasHost && settings?.backupNasShare
                  ? `\\\\${settings.backupNasHost}\\${settings.backupNasShare}`
                  : 'Nicht gespeichert'}
              </dd>
            </div>

            <div>
              <dt className="text-gray-500 dark:text-gray-400">
                Backup-Kennwort
              </dt>
              <dd className="mt-1 flex items-center gap-x-2 font-medium text-gray-900 dark:text-white">
                <KeyIcon aria-hidden="true" className="size-4 text-gray-400" />
                {settings?.backupNasPasswordConfigured
                  ? 'Gespeichert'
                  : 'Nicht gespeichert'}
              </dd>
            </div>

            {settings?.tokenExpiresAt && (
              <div>
                <dt className="text-gray-500 dark:text-gray-400">
                  Token gültig bis
                </dt>
                <dd className="mt-1 font-medium text-gray-900 dark:text-white">
                  {formatUpdatedAt(settings.tokenExpiresAt)}
                </dd>
              </div>
            )}

            {settings?.serverName && (
              <div className="min-w-0">
                <dt className="text-gray-500 dark:text-gray-400">
                  Recording-Server
                </dt>
                <dd className="mt-1 break-all font-medium text-gray-900 dark:text-white">
                  {settings.serverName}
                </dd>
              </div>
            )}

            {settings?.serverVersion && (
              <div>
                <dt className="text-gray-500 dark:text-gray-400">
                  Version
                </dt>
                <dd className="mt-1 font-medium text-gray-900 dark:text-white">
                  {settings.serverVersion}
                </dd>
              </div>
            )}

            {updatedAt && (
              <div>
                <dt className="text-gray-500 dark:text-gray-400">
                  Zuletzt geändert
                </dt>
                <dd className="mt-1 font-medium text-gray-900 dark:text-white">
                  {updatedAt}
                </dd>
              </div>
            )}
          </dl>
        </aside>
      </div>
    </>
  )
}

function normalizeBackupPath(value: string) {
  return value.trim().replace(/\//g, '\\').replace(/[\\]+$/, '').toLowerCase()
}

function normalizeBackupDisplayPath(value: string, nasHost?: string) {
  const cleanValue = value.trim().replace(/\//g, '\\').replace(/[\\]+$/, '')

  if (!cleanValue) {
    return ''
  }

  if (cleanValue.startsWith('\\\\')) {
    return `\\\\${cleanValue.replace(/^\\+/, '')}`
  }

  const cleanHost = nasHost?.trim().replace(/\//g, '\\').replace(/[\\]+$/, '')
  if (
    cleanHost &&
    cleanValue.toLowerCase().startsWith(`${cleanHost.toLowerCase()}\\`)
  ) {
    return `\\\\${cleanValue}`
  }

  return cleanValue
}

function isBackupPathInsideRoot(path: string, rootPath: string) {
  const normalizedPath = normalizeBackupPath(path)
  const normalizedRoot = normalizeBackupPath(rootPath)

  return (
    normalizedPath === normalizedRoot ||
    normalizedPath.startsWith(`${normalizedRoot}\\`)
  )
}

function backupFolderNameFromPath(path: string) {
  const cleanPath = path.trim().replace(/[\\/]+$/, '')
  const parts = cleanPath.split(/[\\/]+/).filter(Boolean)

  return parts[parts.length - 1] || cleanPath || path
}

function backupFolderNodeFromItem(item: BackupFolderItem): TreeListNode {
  const name = item.displayName?.trim() || item.name || backupFolderNameFromPath(item.path)

  return {
    id: item.path,
    name,
    path: item.path,
    children: item.hasChildren ? [] : undefined,
  }
}

function joinBackupPath(basePath: string, childName: string) {
  const base = basePath.trim().replace(/[\\/]+$/, '')
  const child = childName.trim().replace(/^[\\/]+/, '')

  if (!base) {
    return child
  }

  if (!child) {
    return base
  }

  return `${base}\\${child}`
}

function buildBackupFolderBranch(
  rootPath: string,
  currentPath: string,
  folders: BackupFolderItem[],
): TreeListNode[] {
  const cleanRootPath = rootPath.trim().replace(/[\\/]+$/, '')
  const cleanCurrentPath = currentPath.trim().replace(/[\\/]+$/, '')
  const folderNodes = folders.map(backupFolderNodeFromItem)

  if (
    !cleanRootPath ||
    !cleanCurrentPath ||
    normalizeBackupPath(cleanRootPath) === normalizeBackupPath(cleanCurrentPath)
  ) {
    return folderNodes
  }

  if (!isBackupPathInsideRoot(cleanCurrentPath, cleanRootPath)) {
    return []
  }

  let relativePath = cleanCurrentPath
  if (normalizeBackupPath(relativePath).startsWith(normalizeBackupPath(cleanRootPath))) {
    relativePath = relativePath.slice(cleanRootPath.length)
  }

  const segments = relativePath.replace(/^[\\/]+/, '').split(/[\\/]+/).filter(Boolean)

  if (segments.length === 0) {
    return folderNodes
  }

  let runningPath = cleanRootPath
  let rootBranchNode: TreeListNode | null = null
  let currentNode: TreeListNode | null = null

  for (const segment of segments) {
    runningPath = joinBackupPath(runningPath, segment)

    const nextNode: TreeListNode = {
      id: runningPath,
      name: segment,
      path: runningPath,
      children: [],
    }

    if (!rootBranchNode) {
      rootBranchNode = nextNode
    }

    if (currentNode) {
      currentNode.children = [nextNode]
    }

    currentNode = nextNode
  }

  if (currentNode) {
    currentNode.children = folderNodes
  }

  return rootBranchNode ? [rootBranchNode] : folderNodes
}

function buildBackupFolderTree(response: BackupFoldersResponse): TreeListNode[] {
  const roots = Array.isArray(response.roots) ? response.roots : []
  const folders = Array.isArray(response.folders) ? response.folders : []

  if (roots.length === 0) {
    return folders.map(backupFolderNodeFromItem)
  }

  return roots.map((root) => ({
    id: root.path,
    name: root.label || root.path,
    path: root.path,
    children: isBackupPathInsideRoot(response.path, root.path)
      ? buildBackupFolderBranch(root.path, response.path, folders)
      : [],
  }))
}

function getBackupShareFromPath(path: string) {
  const cleanPath = normalizeBackupDisplayPath(path)

  if (!cleanPath.startsWith('\\\\')) {
    return ''
  }

  const parts = cleanPath.slice(2).split('\\').filter(Boolean)

  return parts[1] ?? ''
}

function BackupFolderPicker({
  value,
  nasHost,
  nasShare,
  nasUsername,
  nasPassword,
  storedPasswordAvailable,
  onChange,
  onShareChange,
  disabled,
}: {
  value: string
  nasHost: string
  nasShare: string
  nasUsername: string
  nasPassword: string
  storedPasswordAvailable: boolean
  onChange: (value: string) => void
  onShareChange: (value: string) => void
  disabled?: boolean
}) {
  const [nodes, setNodes] = useState<TreeListNode[]>([])
  const [rootPath, setRootPath] = useState('')
  const [currentPath, setCurrentPath] = useState('')
  const [error, setError] = useState<string | null>(null)
  const [isLoading, setIsLoading] = useState(false)
  const loadAbortControllerRef = useRef<AbortController | null>(null)
  const canLoadBackupFolders =
    Boolean(nasHost.trim()) &&
    Boolean(nasUsername.trim()) &&
    (Boolean(nasPassword) || storedPasswordAvailable)

  useEffect(() => {
    return () => {
      loadAbortControllerRef.current?.abort()
    }
  }, [])

  async function loadFolders(path?: string, shareOverride?: string) {
    const cleanHost = nasHost.trim()
    const cleanUsername = nasUsername.trim()
    const requestedPath = normalizeBackupDisplayPath(path ?? '', cleanHost)
    const pathShare = getBackupShareFromPath(requestedPath)
    const requestedShare = shareOverride ?? (pathShare || nasShare.trim())

    if (!cleanHost || !cleanUsername || (!nasPassword && !storedPasswordAvailable)) {
      setNodes([])
      setRootPath('')
      setCurrentPath('')
      setError(null)
      return
    }

    setIsLoading(true)
    setError(null)

    loadAbortControllerRef.current?.abort()
    const abortController = new AbortController()
    loadAbortControllerRef.current = abortController
    const timeoutId = window.setTimeout(() => {
      abortController.abort()
    }, 20000)

    try {
      const response = await fetch(
        `${API_URL}/admin/milestone/backup-folders/browse`,
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          credentials: 'include',
          signal: abortController.signal,
          body: JSON.stringify({
            nasHost: cleanHost,
            nasShare: requestedShare,
            nasUsername: cleanUsername,
            nasPassword,
            path: requestedPath,
          }),
        },
      )

      if (!response.ok) {
        setError(
          await readApiError(
            response,
            'Backup-Ordner konnten nicht geladen werden.',
          ),
        )
        return
      }

      const data = (await response.json()) as BackupFoldersResponse
      const nextRootPath = data.roots?.[0]?.path ?? ''
      const nextPath = data.path || requestedPath || nextRootPath
      const nextShare = getBackupShareFromPath(nextPath || nextRootPath)

      setNodes(buildBackupFolderTree(data))
      setRootPath(nextRootPath)
      setCurrentPath(nextPath)
      setError(data.warning ?? null)

      if (nextShare) {
        onShareChange(nextShare)
      }

      if (nextPath && value.trim() !== nextPath) {
        onChange(nextPath)
      }
    } catch (error) {
      if (abortController.signal.aborted) {
        if (loadAbortControllerRef.current === abortController) {
          setError(
            'NAS hat nicht rechtzeitig geantwortet. Prüfe Hostname, Benutzername, Domäne und Netzwerkverbindung.',
          )
        }
        return
      }

      setError(
        error instanceof Error
          ? error.message
          : 'Backup-Ordner konnten nicht geladen werden.',
      )
    } finally {
      window.clearTimeout(timeoutId)

      if (loadAbortControllerRef.current === abortController) {
        loadAbortControllerRef.current = null
        setIsLoading(false)
      }
    }
  }

  useEffect(() => {
    if (!canLoadBackupFolders) {
      setNodes([])
      setRootPath('')
      setCurrentPath('')
      setError(null)
      return
    }

    const timeoutId = window.setTimeout(() => {
      const selectedPath = normalizeBackupDisplayPath(value, nasHost)
      const selectedShare = selectedPath ? getBackupShareFromPath(selectedPath) : ''

      void loadFolders(selectedPath || undefined, selectedShare || '')
    }, 500)

    return () => {
      window.clearTimeout(timeoutId)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [nasHost, nasUsername, nasPassword, storedPasswordAvailable])

  function handleSelectedPathChange(path: string) {
    const nextPath = normalizeBackupDisplayPath(path, nasHost)
    const nextShare = getBackupShareFromPath(nextPath)
    if (nextShare) {
      onShareChange(nextShare)
    }

    onChange(nextPath)
    void loadFolders(nextPath, nextShare)
  }

  function handleLoadShares() {
    onShareChange('')
    onChange('')
    setRootPath('')
    setCurrentPath('')
    void loadFolders('', '')
  }

  return (
    <div>
      {error && (
        <div className="mb-3 rounded-md bg-amber-50 p-3 text-sm text-amber-800 ring-1 ring-amber-200 dark:bg-amber-500/10 dark:text-amber-200 dark:ring-amber-400/20">
          {error}
        </div>
      )}

      <div className="mb-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
        <p className="text-sm text-gray-500 dark:text-gray-400">
          Lade die Freigaben des NAS und wähle darunter den Ordner mit den
          Robocopy-.log-Dateien aus.
        </p>

        <Button
          type="button"
          variant="secondary"
          color="indigo"
          size="sm"
          isLoading={isLoading}
          disabled={disabled || isLoading || !canLoadBackupFolders}
          onClick={handleLoadShares}
        >
          Neu laden
        </Button>
      </div>

      <TreeList
        label="Freigabe und Ordner auswählen"
        description={
          canLoadBackupFolders
            ? 'Freigabe auswählen, danach den passenden Ordner darunter anklicken.'
            : 'Trage NAS-IP, Benutzername und Kennwort ein.'
        }
        selectedPath={value || currentPath}
        nodes={nodes}
        rootPath={rootPath || currentPath || value}
        onSelectedPathChange={handleSelectedPathChange}
        disabled={disabled || isLoading || !canLoadBackupFolders}
        showFallbackNodes
        emptyMessage="Noch keine Freigaben geladen."
        showNewFolderInput={false}
        pathPlaceholder="\\\\NAS\\Freigabe\\Ordner"
      />
    </div>
  )
}
