import {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ChangeEvent,
  type FormEvent,
} from 'react'
import {
  ClipboardDocumentIcon,
  HashtagIcon,
  KeyIcon,
  MagnifyingGlassIcon,
  PlusIcon,
} from '@heroicons/react/20/solid'
import Button from '../../../components/Button'
import Avatar from '../../../components/Avatar'
import Checkbox from '../../../components/Checkbox'
import LoadingSpinner from '../../../components/LoadingSpinner'
import Switch from '../../../components/Switch'
import { useNotifications } from '../../../components/Notifications'

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

type AdminChannel = {
  id: string
  name: string
  slug: string
  sourceName: string
  image: string
  allUsers: boolean
  userIds: string[]
  webhookEnabled: boolean
  tokenConfigured: boolean
  webhookTokenUpdatedAt?: string | null
  webhookPath: string
}

type AdminChannelUser = {
  id: string
  username: string
  displayName: string
  email: string
  unit: string
  avatar: string
}

type ChannelDraft = {
  id?: string
  name: string
  slug: string
  sourceName: string
  image: string
  allUsers: boolean
  userIds: string[]
  webhookEnabled: boolean
  tokenConfigured: boolean
  webhookTokenUpdatedAt?: string | null
  webhookPath: string
}

const inputClassName =
  'block w-full rounded-md bg-white px-3 py-1.5 text-sm 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 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:focus:outline-indigo-500'

const emptyDraft: ChannelDraft = {
  name: '',
  slug: '',
  sourceName: '',
  image: '',
  allUsers: true,
  userIds: [],
  webhookEnabled: true,
  tokenConfigured: false,
  webhookPath: '',
}

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

function userDisplayName(user: AdminChannelUser) {
  return user.displayName || user.username || user.email
}

function channelToDraft(channel: AdminChannel): ChannelDraft {
  return {
    ...channel,
    userIds: channel.userIds ?? [],
  }
}

export default function ChannelsAdministration() {
  const { showToast, showErrorToast } = useNotifications()
  const [channels, setChannels] = useState<AdminChannel[]>([])
  const [users, setUsers] = useState<AdminChannelUser[]>([])
  const [draft, setDraft] = useState<ChannelDraft>(emptyDraft)
  const [zabbixScript, setZabbixScript] = useState('')
  const [newToken, setNewToken] = useState('')
  const [memberSearch, setMemberSearch] = useState('')
  const [isLoading, setIsLoading] = useState(true)
  const [isSaving, setIsSaving] = useState(false)
  const [isRotatingToken, setIsRotatingToken] = useState(false)
  const imageInputRef = useRef<HTMLInputElement | null>(null)

  const webhookUrl = useMemo(() => {
    const path = draft.webhookPath || `/webhooks/channels/${draft.slug}`
    try {
      return new URL(path, API_URL).toString()
    } catch {
      return `${API_URL.replace(/\/$/, '')}${path}`
    }
  }, [draft.slug, draft.webhookPath])

  const filteredUsers = useMemo(() => {
    const query = memberSearch.trim().toLowerCase()
    if (!query) {
      return users
    }

    return users.filter((user) =>
      [userDisplayName(user), user.username, user.email, user.unit]
        .filter(Boolean)
        .join(' ')
        .toLowerCase()
        .includes(query),
    )
  }, [memberSearch, users])

  const loadChannels = useCallback(async (preferredId?: string) => {
    setIsLoading(true)
    try {
      const response = await fetch(`${API_URL}/admin/channels`, {
        credentials: 'include',
      })
      if (!response.ok) {
        throw new Error(
          await readApiError(response, 'Channels konnten nicht geladen werden'),
        )
      }

      const data = await response.json()
      const nextChannels = (data.channels ?? []) as AdminChannel[]
      setChannels(nextChannels)
      setUsers(data.users ?? [])
      setZabbixScript(data.zabbixScript ?? '')

      const selected =
        nextChannels.find((channel) => channel.id === preferredId) ??
        nextChannels[0]
      setDraft(selected ? channelToDraft(selected) : emptyDraft)
    } catch (error) {
      showErrorToast({
        title: 'Channels konnten nicht geladen werden',
        error,
        fallback: 'Channels konnten nicht geladen werden',
      })
    } finally {
      setIsLoading(false)
    }
  }, [showErrorToast])

  useEffect(() => {
    const timeoutId = window.setTimeout(() => {
      void loadChannels()
    }, 0)

    return () => window.clearTimeout(timeoutId)
  }, [loadChannels])

  function selectChannel(channel: AdminChannel) {
    setDraft(channelToDraft(channel))
    setNewToken('')
    setMemberSearch('')
  }

  function createChannel() {
    setDraft(emptyDraft)
    setNewToken('')
    setMemberSearch('')
  }

  function updateDraft<Key extends keyof ChannelDraft>(
    key: Key,
    value: ChannelDraft[Key],
  ) {
    setDraft((current) => ({ ...current, [key]: value }))
  }

  function toggleUser(userId: string, checked: boolean) {
    setDraft((current) => ({
      ...current,
      userIds: checked
        ? [...new Set([...current.userIds, userId])]
        : current.userIds.filter((id) => id !== userId),
    }))
  }

  function setAllMembers(checked: boolean) {
    const filteredIds = filteredUsers.map((user) => user.id)
    setDraft((current) => {
      if (checked) {
        return {
          ...current,
          userIds: [...new Set([...current.userIds, ...filteredIds])],
        }
      }

      const filteredSet = new Set(filteredIds)
      return {
        ...current,
        userIds: current.userIds.filter((id) => !filteredSet.has(id)),
      }
    })
  }

  function handleImageFileChange(event: ChangeEvent<HTMLInputElement>) {
    const file = event.target.files?.[0]
    event.target.value = ''

    if (!file) {
      return
    }
    if (!file.type.startsWith('image/')) {
      showToast({
        title: 'Ungültige Bilddatei',
        message: 'Bitte wähle eine gültige Bilddatei aus.',
        variant: 'error',
      })
      return
    }
    if (file.size > 2 * 1024 * 1024) {
      showToast({
        title: 'Channel-Bild zu groß',
        message: 'Das Channel-Bild darf maximal 2 MB groß sein.',
        variant: 'error',
      })
      return
    }

    const reader = new FileReader()
    reader.onload = () => {
      if (typeof reader.result === 'string') {
        updateDraft('image', reader.result)
      }
    }
    reader.readAsDataURL(file)
  }

  async function saveChannel(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    setIsSaving(true)

    try {
      const response = await fetch(
        draft.id
          ? `${API_URL}/admin/channels/${draft.id}`
          : `${API_URL}/admin/channels`,
        {
          method: draft.id ? 'PUT' : 'POST',
          credentials: 'include',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            name: draft.name,
            slug: draft.slug,
            sourceName: draft.sourceName,
            image: draft.image,
            allUsers: draft.allUsers,
            userIds: draft.userIds,
            webhookEnabled: draft.webhookEnabled,
          }),
        },
      )
      if (!response.ok) {
        throw new Error(
          await readApiError(response, 'Channel konnte nicht gespeichert werden'),
        )
      }

      const data = await response.json()
      await loadChannels(data.id ?? draft.id)
      showToast({
        title: 'Channel gespeichert',
        message: 'Mitglieder und Webhook-Einstellungen wurden aktualisiert.',
        variant: 'success',
      })
    } catch (error) {
      showErrorToast({
        title: 'Channel konnte nicht gespeichert werden',
        error,
        fallback: 'Channel konnte nicht gespeichert werden',
      })
    } finally {
      setIsSaving(false)
    }
  }

  async function rotateToken() {
    if (!draft.id) {
      return
    }
    setIsRotatingToken(true)

    try {
      const response = await fetch(
        `${API_URL}/admin/channels/${draft.id}/token`,
        {
          method: 'POST',
          credentials: 'include',
        },
      )
      if (!response.ok) {
        throw new Error(
          await readApiError(response, 'Webhook-Token konnte nicht erzeugt werden'),
        )
      }

      const data = await response.json()
      setNewToken(data.token ?? '')
      setDraft((current) => ({ ...current, tokenConfigured: true }))
      showToast({
        title: 'Webhook-Token erzeugt',
        message: 'Das Token wird nur jetzt vollständig angezeigt.',
        variant: 'success',
        autoClose: false,
      })
      await loadChannels(draft.id)
      setNewToken(data.token ?? '')
    } catch (error) {
      showErrorToast({
        title: 'Webhook-Token konnte nicht erzeugt werden',
        error,
        fallback: 'Webhook-Token konnte nicht erzeugt werden',
      })
    } finally {
      setIsRotatingToken(false)
    }
  }

  async function copyText(value: string, label: string) {
    try {
      await navigator.clipboard.writeText(value)
      showToast({
        title: `${label} kopiert`,
        message: 'Der Wert liegt jetzt in der Zwischenablage.',
        variant: 'success',
      })
    } catch (error) {
      showErrorToast({
        title: `${label} konnte nicht kopiert werden`,
        error,
        fallback: 'Die Zwischenablage ist nicht verfügbar.',
      })
    }
  }

  if (isLoading) {
    return (
      <div className="rounded-xl bg-white p-8 shadow-sm ring-1 ring-gray-200 dark:bg-gray-900 dark:ring-white/10">
        <LoadingSpinner label="Channels werden geladen..." center />
      </div>
    )
  }

  const selectedMemberCount = draft.userIds.length
  const filteredAllSelected =
    filteredUsers.length > 0 &&
    filteredUsers.every((user) => draft.userIds.includes(user.id))
  const filteredSomeSelected =
    !filteredAllSelected &&
    filteredUsers.some((user) => draft.userIds.includes(user.id))

  return (
    <div className="grid grid-cols-1 gap-6 xl:grid-cols-[19rem_minmax(0,1fr)]">
      <aside className="overflow-hidden rounded-xl bg-white shadow-sm ring-1 ring-gray-200 dark:bg-gray-900 dark:ring-white/10">
        <div className="flex items-center justify-between border-b border-gray-200 p-4 dark:border-white/10">
          <div>
            <h2 className="text-sm font-semibold text-gray-900 dark:text-white">
              Channels
            </h2>
            <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
              Schreibgeschützte Integrationen
            </p>
          </div>
          <Button
            type="button"
            size="sm"
            color="indigo"
            leadingIcon={<PlusIcon className="size-4" />}
            onClick={createChannel}
          >
            Neu
          </Button>
        </div>

        <div className="space-y-1 p-2">
          {channels.map((channel) => (
            <button
              key={channel.id}
              type="button"
              onClick={() => selectChannel(channel)}
              className={`flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left text-sm ${
                draft.id === channel.id
                  ? 'bg-indigo-50 text-indigo-700 dark:bg-indigo-500/10 dark:text-indigo-300'
                  : 'text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-white/5'
              }`}
            >
              {channel.image ? (
                <Avatar
                  src={channel.image}
                  name={channel.name}
                  size={9}
                  shape="circle"
                />
              ) : (
                <HashtagIcon className="size-5 shrink-0" />
              )}
              <span className="min-w-0">
                <span className="block truncate font-medium">{channel.name}</span>
                <span className="block truncate text-xs opacity-70">
                  /{channel.slug}
                </span>
              </span>
            </button>
          ))}
        </div>
      </aside>

      <div className="space-y-6">
        <form
          onSubmit={saveChannel}
          className="rounded-xl bg-white p-5 shadow-sm ring-1 ring-gray-200 dark:bg-gray-900 dark:ring-white/10"
        >
          <h2 className="text-base font-semibold text-gray-900 dark:text-white">
            {draft.id ? 'Channel bearbeiten' : 'Channel anlegen'}
          </h2>

          <div className="mt-5 grid gap-4 sm:grid-cols-2">
            <label className="text-sm font-medium text-gray-900 dark:text-white">
              Channelname
              <input
                required
                value={draft.name}
                onChange={(event) => updateDraft('name', event.target.value)}
                placeholder="Zabbix"
                className={`${inputClassName} mt-2`}
              />
            </label>

            <label className="text-sm font-medium text-gray-900 dark:text-white">
              Webhook-Slug
              <input
                required
                value={draft.slug}
                onChange={(event) => updateDraft('slug', event.target.value)}
                placeholder="zabbix"
                pattern="[a-z0-9]+(?:-[a-z0-9]+)*"
                className={`${inputClassName} mt-2`}
              />
            </label>

            <label className="text-sm font-medium text-gray-900 dark:text-white sm:col-span-2">
              Angezeigter Absender
              <input
                value={draft.sourceName}
                onChange={(event) => updateDraft('sourceName', event.target.value)}
                placeholder={draft.name || 'Zabbix'}
                className={`${inputClassName} mt-2`}
              />
            </label>

            <div className="sm:col-span-2">
              <p className="text-sm font-medium text-gray-900 dark:text-white">
                Channel-Bild
              </p>
              <div className="mt-2 flex items-center gap-4">
                <Avatar
                  src={draft.image}
                  name={draft.name || 'Channel'}
                  size={14}
                  shape="circle"
                />
                <div>
                  <input
                    ref={imageInputRef}
                    type="file"
                    accept="image/png,image/jpeg,image/webp,image/gif"
                    className="sr-only"
                    onChange={handleImageFileChange}
                  />
                  <div className="flex flex-wrap gap-2">
                    <Button
                      type="button"
                      variant="secondary"
                      color="gray"
                      size="sm"
                      onClick={() => imageInputRef.current?.click()}
                    >
                      Bild hochladen
                    </Button>
                    {draft.image && (
                      <Button
                        type="button"
                        variant="secondary"
                        color="red"
                        size="sm"
                        onClick={() => updateDraft('image', '')}
                      >
                        Entfernen
                      </Button>
                    )}
                  </div>
                  <p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
                    Optional: PNG, JPG, WEBP oder GIF bis maximal 2 MB.
                  </p>
                </div>
              </div>
            </div>
          </div>

          <div className="mt-5 space-y-4 border-t border-gray-200 pt-5 dark:border-white/10">
            <Switch
              checked={draft.webhookEnabled}
              onChange={(event) =>
                updateDraft('webhookEnabled', event.target.checked)
              }
              label="Webhook aktiviert"
              description="Nur aktivierte Channels nehmen externe Nachrichten an."
            />
            <Switch
              checked={draft.allUsers}
              onChange={(event) => updateDraft('allUsers', event.target.checked)}
              label="Für alle Benutzer"
              description="Neue Benutzer werden automatisch Mitglied dieses Channels."
            />
          </div>

          {!draft.allUsers && (
            <div className="mt-5">
              <div className="flex flex-wrap items-center justify-between gap-2">
                <h3 className="text-sm font-medium text-gray-900 dark:text-white">
                  Channelmitglieder
                </h3>
                <span className="text-xs text-gray-500 dark:text-gray-400">
                  {selectedMemberCount} von {users.length} ausgewählt
                </span>
              </div>

              <div className="relative mt-3">
                <MagnifyingGlassIcon className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-gray-400" />
                <input
                  type="search"
                  value={memberSearch}
                  onChange={(event) => setMemberSearch(event.target.value)}
                  placeholder="Mitglieder suchen…"
                  className="block w-full rounded-md bg-white py-1.5 pr-3 pl-9 text-sm 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 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:focus:outline-indigo-500"
                />
              </div>

              <div className="mt-3 overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
                <div className="flex items-center justify-between gap-3 border-b border-gray-200 bg-gray-50 px-3 py-2 dark:border-white/10 dark:bg-white/5">
                  <Checkbox
                    checked={filteredAllSelected}
                    indeterminate={filteredSomeSelected}
                    disabled={filteredUsers.length === 0}
                    onChange={(event) => setAllMembers(event.target.checked)}
                    label={
                      memberSearch.trim()
                        ? 'Alle Treffer auswählen'
                        : 'Alle auswählen'
                    }
                    labelClassName="font-medium"
                  />
                  {selectedMemberCount > 0 && (
                    <button
                      type="button"
                      onClick={() => updateDraft('userIds', [])}
                      className="shrink-0 text-xs font-medium text-indigo-600 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300"
                    >
                      Auswahl zurücksetzen
                    </button>
                  )}
                </div>

                <div className="max-h-72 space-y-0.5 overflow-y-auto p-2">
                  {filteredUsers.length === 0 ? (
                    <p className="px-2 py-8 text-center text-sm text-gray-500 dark:text-gray-400">
                      Keine Benutzer gefunden.
                    </p>
                  ) : (
                    filteredUsers.map((user) => {
                      const checked = draft.userIds.includes(user.id)
                      return (
                        <Checkbox
                          key={user.id}
                          checked={checked}
                          onChange={(event) =>
                            toggleUser(user.id, event.target.checked)
                          }
                          wrapperClassName={`items-center rounded-lg px-2 py-2 transition ${
                            checked
                              ? 'bg-indigo-50 dark:bg-indigo-500/10'
                              : 'hover:bg-gray-50 dark:hover:bg-white/5'
                          }`}
                          label={
                            <span className="flex min-w-0 items-center gap-3">
                              <Avatar
                                src={user.avatar}
                                name={userDisplayName(user)}
                                size={8}
                                className="shrink-0"
                              />
                              <span className="min-w-0">
                                <span className="block truncate font-medium text-gray-900 dark:text-white">
                                  {userDisplayName(user)}
                                </span>
                                <span className="block truncate text-xs text-gray-500 dark:text-gray-400">
                                  {user.unit || user.email}
                                </span>
                              </span>
                            </span>
                          }
                        />
                      )
                    })
                  )}
                </div>
              </div>
            </div>
          )}

          <div className="mt-6 flex justify-end">
            <Button type="submit" color="indigo" isLoading={isSaving}>
              Channel speichern
            </Button>
          </div>
        </form>

        {draft.id && (
          <section className="rounded-xl bg-white p-5 shadow-sm ring-1 ring-gray-200 dark:bg-gray-900 dark:ring-white/10">
            <div className="flex flex-wrap items-start justify-between gap-4">
              <div>
                <h2 className="flex items-center gap-2 text-base font-semibold text-gray-900 dark:text-white">
                  <KeyIcon className="size-5 text-indigo-500" />
                  Webhook für Zabbix 7.2.15
                </h2>
                <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
                  Verwende den Medientyp „Webhook“. Das Bearer-Token wird nur nach
                  dem Erzeugen vollständig angezeigt.
                </p>
              </div>
              <Button
                type="button"
                variant="secondary"
                color="indigo"
                isLoading={isRotatingToken}
                onClick={() => void rotateToken()}
              >
                {draft.tokenConfigured ? 'Token erneuern' : 'Token erzeugen'}
              </Button>
            </div>

            <div className="mt-5 space-y-4">
              <ol className="list-decimal space-y-1.5 pl-5 text-sm text-gray-600 dark:text-gray-300">
                <li>
                  Unter „Alerts &gt; Media types“ einen Medientyp vom Typ
                  „Webhook“ anlegen.
                </li>
                <li>
                  Die Parameter und das JavaScript unten übernehmen und den
                  Medientyp über „Test“ prüfen.
                </li>
                <li>
                  Den Medientyp einem Zabbix-Benutzer als Medium zuweisen. Bei
                  „Send to“ genügt beispielsweise „teg-channel“.
                </li>
                <li>
                  Diesen Benutzer in der gewünschten Action unter „Send to
                  users“ als Empfänger auswählen.
                </li>
              </ol>

              <CopyField
                label="URL"
                value={webhookUrl}
                onCopy={() => void copyText(webhookUrl, 'URL')}
              />
              {newToken && (
                <CopyField
                  label="Token"
                  value={newToken}
                  onCopy={() => void copyText(newToken, 'Token')}
                />
              )}

              <div>
                <h3 className="text-sm font-medium text-gray-900 dark:text-white">
                  Parameter im Zabbix-Medientyp
                </h3>
                <div className="mt-2 overflow-hidden rounded-lg border border-gray-200 text-sm dark:border-white/10">
                  {[
                    ['URL', webhookUrl],
                    ['Token', newToken || '<erzeugtes Token>'],
                    ['To', '{ALERT.SENDTO}'],
                    ['Subject', '{ALERT.SUBJECT}'],
                    ['Message', '{ALERT.MESSAGE}'],
                    ['Host', '{HOST.NAME}'],
                    ['Severity', '{EVENT.SEVERITY}'],
                    ['Status', '{EVENT.STATUS}'],
                    ['EventId', '{EVENT.ID}'],
                    ['EventUrl', '{TRIGGER.URL}'],
                  ].map(([name, value]) => (
                    <div
                      key={name}
                      className="grid grid-cols-[7rem_minmax(0,1fr)] border-b border-gray-200 last:border-b-0 dark:border-white/10"
                    >
                      <span className="bg-gray-50 px-3 py-2 font-medium text-gray-700 dark:bg-white/5 dark:text-gray-300">
                        {name}
                      </span>
                      <code className="break-all px-3 py-2 text-gray-600 dark:text-gray-300">
                        {value}
                      </code>
                    </div>
                  ))}
                </div>
              </div>
            </div>
          </section>
        )}
      </div>
    </div>
  )
}

function CopyField({
  label,
  value,
  onCopy,
}: {
  label: string
  value: string
  onCopy: () => void
}) {
  return (
    <div>
      <label className="block text-sm font-medium text-gray-900 dark:text-white">
        {label}
      </label>
      <div className="mt-2 flex gap-2">
        <input readOnly value={value} className={inputClassName} />
        <Button
          type="button"
          variant="secondary"
          color="gray"
          leadingIcon={<ClipboardDocumentIcon className="size-4" />}
          onClick={onCopy}
        >
          Kopieren
        </Button>
      </div>
    </div>
  )
}
