import React, { useCallback, useRef, useState } from 'react';
import {
  Box,
  Button,
  Checkbox,
  Dialog,
  Header,
  Icon,
  IconButton,
  Icons,
  Input,
  Overlay,
  OverlayBackdrop,
  OverlayCenter,
  Scroll,
  Spinner,
  Text,
  color,
  config,
} from 'folds';
import FocusTrap from 'focus-trap-react';
import { Page, PageContent, PageHeader } from '../../components/page';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRoom } from '../../hooks/useRoom';
import { useStateEvent } from '../../hooks/useStateEvent';
import { StateEvent } from '../../../types/matrix/room';
import { sendStateEvent } from '../../utils/room';
import { usePowerLevels, readPowerLevel } from '../../hooks/usePowerLevels';
import { useRoomCreators } from '../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { SequenceCard } from '../../components/sequence-card';
import { SequenceCardStyle } from '../common-settings/styles.css';
import { stopPropagation } from '../../utils/keyboard';
import { isValidServerPattern, matchesAnyGlob } from '../../utils/serverAcl';
import { MobileTouchTarget } from '../../styles/mobile.css';
import { useModalStyle } from '../../hooks/useModalStyle';

// ── Types ─────────────────────────────────────────────────────────────────────

type ServerAclContent = {
  allow: string[];
  deny: string[];
  allow_ip_literals: boolean;
};

const DEFAULT_ACL: ServerAclContent = {
  allow: ['*'],
  deny: [],
  allow_ip_literals: false,
};

// ── Server list sub-component ─────────────────────────────────────────────────

type ServerListProps = {
  label: string;
  entries: string[];
  canEdit: boolean;
  onAdd: (value: string) => void;
  onRemove: (index: number) => void;
};

function ServerList({ label, entries, canEdit, onAdd, onRemove }: ServerListProps) {
  const inputRef = useRef<HTMLInputElement>(null);
  const [error, setError] = useState<string | undefined>();

  const handleAdd = () => {
    const raw = inputRef.current?.value ?? '';
    const value = raw.trim();
    if (!value) return;

    if (!isValidServerPattern(value)) {
      setError('Invalid pattern. Use a hostname, IP, or glob (e.g. *.evil.com, 1.2.3.*, 10.0.0.?)');
      return;
    }
    setError(undefined);
    onAdd(value);
    if (inputRef.current) inputRef.current.value = '';
  };

  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter') handleAdd();
  };

  return (
    <Box direction="Column" gap="100">
      <Box alignItems="Center" justifyContent="SpaceBetween">
        <Text size="L400">{label}</Text>
        {canEdit && (
          <Button
            size="300"
            variant="Secondary"
            fill="Soft"
            radii="300"
            before={<Icon src={Icons.Plus} size="100" />}
            onClick={handleAdd}
          >
            <Text size="B300">Add</Text>
          </Button>
        )}
      </Box>

      {canEdit && (
        <Box direction="Column" gap="100">
          <Input
            ref={inputRef}
            type="text"
            placeholder="e.g. *.example.com or badserver.org"
            onKeyDown={handleKeyDown}
            variant={error ? 'Critical' : 'Secondary'}
            size="400"
            radii="300"
          />
          {error && (
            <Text size="T200" style={{ color: color.Critical.Main }}>
              {error}
            </Text>
          )}
        </Box>
      )}

      <SequenceCard
        className={SequenceCardStyle}
        variant="SurfaceVariant"
        direction="Column"
        gap="0"
      >
        {entries.length === 0 ? (
          <Text size="T300" priority="300" style={{ padding: `${config.space.S100} 0` }}>
            (empty)
          </Text>
        ) : (
          entries.map((entry, i) => (
            <Box
              key={`${entry}-${i}`}
              alignItems="Center"
              justifyContent="SpaceBetween"
              style={{
                padding: `${config.space.S100} 0`,
                borderBottom:
                  i < entries.length - 1 ? `1px solid ${color.Surface.ContainerLine}` : undefined,
              }}
            >
              <Text size="T300" style={{ fontFamily: 'monospace', wordBreak: 'break-all' }}>
                {entry}
              </Text>
              {canEdit && (
                <IconButton
                  size="300"
                  variant="Background"
                  radii="300"
                  className={MobileTouchTarget}
                  aria-label={`Remove ${entry}`}
                  onClick={() => onRemove(i)}
                  style={{ flexShrink: 0 }}
                >
                  <Icon src={Icons.Cross} size="100" />
                </IconButton>
              )}
            </Box>
          ))
        )}
      </SequenceCard>
    </Box>
  );
}

// ── Main component ────────────────────────────────────────────────────────────

type RoomServerACLProps = {
  requestClose: () => void;
};

export function RoomServerACL({ requestClose }: RoomServerACLProps) {
  const mx = useMatrixClient();
  const room = useRoom();
  const modalStyle = useModalStyle(480);

  // Power level checks
  const powerLevels = usePowerLevels(room);
  const creators = useRoomCreators(room);
  const permissions = useRoomPermissions(creators, powerLevels);
  const myUserId = mx.getSafeUserId();
  const canEdit = permissions.stateEvent(StateEvent.RoomServerAcl, myUserId);

  // Read current ACL from room state
  const aclEvent = useStateEvent(room, StateEvent.RoomServerAcl);
  const currentAcl = aclEvent?.getContent<ServerAclContent>() ?? DEFAULT_ACL;

  // Local draft state — initialised from current ACL
  const [allowList, setAllowList] = useState<string[]>(() => currentAcl.allow ?? ['*']);
  const [denyList, setDenyList] = useState<string[]>(() => currentAcl.deny ?? []);
  const [allowIpLiterals, setAllowIpLiterals] = useState<boolean>(
    () => currentAcl.allow_ip_literals ?? false,
  );

  // Track whether there are unsaved changes
  const isDirty =
    JSON.stringify(allowList) !== JSON.stringify(currentAcl.allow ?? ['*']) ||
    JSON.stringify(denyList) !== JSON.stringify(currentAcl.deny ?? []) ||
    allowIpLiterals !== (currentAcl.allow_ip_literals ?? false);

  // Save handler
  const [saveState, save] = useAsyncCallback(
    useCallback(async () => {
      await sendStateEvent(mx, room.roomId, StateEvent.RoomServerAcl, {
        allow: allowList,
        deny: denyList,
        allow_ip_literals: allowIpLiterals,
      });
    }, [mx, room.roomId, allowList, denyList, allowIpLiterals]),
  );

  const saving = saveState.status === AsyncStatus.Loading;
  const saveError =
    saveState.status === AsyncStatus.Error ? 'Failed to save ACL. Please try again.' : undefined;

  // ── Save guards ───────────────────────────────────────────────────────────
  // #1 Empty allow list denies EVERY server (allow: [] is not "allow all") and
  // partitions the room from all federation irreversibly — block the save.
  const emptyAllow = allowList.length === 0;

  // #2 Self-ban: the local homeserver must match at least one allow glob and no
  // deny glob, otherwise applying this ACL removes our own server from the room.
  const localDomain = mx.getDomain() ?? '';
  const selfBanned =
    localDomain.length > 0 &&
    (!matchesAnyGlob(localDomain, allowList) || matchesAnyGlob(localDomain, denyList));

  // #4 Gate the destructive write behind a confirmation dialog.
  const [prompt, setPrompt] = useState(false);

  const handleConfirmSave = () => {
    setPrompt(false);
    save();
  };

  // Required power level for this state event
  const requiredPL = readPowerLevel.state(powerLevels, StateEvent.RoomServerAcl);
  const myPL = readPowerLevel.user(powerLevels, myUserId);

  return (
    <Page>
      <PageHeader outlined={false}>
        <Box grow="Yes" gap="200">
          <Box grow="Yes" alignItems="Center" gap="200">
            <Icon src={Icons.Shield} size="200" />
            <Text as="h2" size="H3" truncate>
              Server ACL
            </Text>
          </Box>
          <Box shrink="No" gap="200" alignItems="Center">
            {canEdit && (
              <Button
                size="400"
                variant="Primary"
                fill="Solid"
                radii="300"
                disabled={saving || !isDirty || emptyAllow}
                onClick={() => setPrompt(true)}
                before={saving ? <Spinner size="200" /> : <Icon src={Icons.Check} size="100" />}
              >
                <Text size="B400">{saving ? 'Saving…' : 'Save Changes'}</Text>
              </Button>
            )}
            <IconButton onClick={requestClose} variant="Surface" aria-label="Close">
              <Icon src={Icons.Cross} />
            </IconButton>
          </Box>
        </Box>
      </PageHeader>

      <Box grow="Yes">
        <Scroll hideTrack visibility="Hover">
          <PageContent>
            <Box direction="Column" gap="700">
              {/* Info banner */}
              <SequenceCard
                className={SequenceCardStyle}
                variant="SurfaceVariant"
                direction="Column"
                gap="200"
              >
                <Box gap="200" alignItems="Start">
                  <Icon src={Icons.Warning} size="200" style={{ flexShrink: 0, marginTop: 2 }} />
                  <Box direction="Column" gap="100">
                    <Text size="T300">
                      Server ACL controls which servers can participate in this room. Changes take
                      effect immediately.
                    </Text>
                    {!canEdit && (
                      <Text size="T300" style={{ color: color.Critical.Main }}>
                        You need power level {requiredPL} to edit the ACL (your level: {myPL}).
                      </Text>
                    )}
                  </Box>
                </Box>
              </SequenceCard>

              {/* Save error */}
              {saveError && (
                <Text size="T300" style={{ color: color.Critical.Main }}>
                  {saveError}
                </Text>
              )}

              {/* #1 Empty allow list guard — blocks save */}
              {canEdit && emptyAllow && (
                <Text size="T300" style={{ color: color.Critical.Main }}>
                  The allow list is empty. An empty allow list denies every server and partitions
                  this room from all federation permanently. Add at least one entry (use
                  &quot;*&quot; to allow all servers).
                </Text>
              )}

              {/* #2 Self-ban warning — save allowed but confirmation required */}
              {canEdit && !emptyAllow && selfBanned && (
                <Text size="T300" style={{ color: color.Warning.Main }}>
                  Warning: your own homeserver ({localDomain}) is not permitted by this ACL.
                  Applying it will remove your server from the room and you may lose the ability to
                  moderate it.
                </Text>
              )}

              {/* Allow IP literals toggle */}
              <Box direction="Column" gap="100">
                <Text size="L400">IP Address Access</Text>
                <SequenceCard
                  className={SequenceCardStyle}
                  variant="SurfaceVariant"
                  direction="Column"
                  gap="200"
                >
                  <Box alignItems="Center" gap="300">
                    <Checkbox
                      id="allow-ip-literals"
                      checked={allowIpLiterals}
                      disabled={!canEdit}
                      onClick={() => setAllowIpLiterals(!allowIpLiterals)}
                      size="300"
                      variant="Primary"
                    />
                    <Box direction="Column" gap="0">
                      {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
                      <label
                        htmlFor="allow-ip-literals"
                        style={{ cursor: canEdit ? 'pointer' : 'default' }}
                      >
                        <Text size="T300">Allow IP literal addresses</Text>
                      </label>
                      <Text size="T200" priority="300">
                        When disabled, clients connecting via raw IP addresses (e.g. 1.2.3.4) cannot
                        participate.
                      </Text>
                    </Box>
                  </Box>
                </SequenceCard>
              </Box>

              {/* Allowed servers */}
              <ServerList
                label="Allowed Servers"
                entries={allowList}
                canEdit={canEdit}
                onAdd={(value) => setAllowList((prev) => [...prev, value])}
                onRemove={(index) => setAllowList((prev) => prev.filter((_, i) => i !== index))}
              />

              {/* Denied servers */}
              <ServerList
                label="Denied Servers"
                entries={denyList}
                canEdit={canEdit}
                onAdd={(value) => setDenyList((prev) => [...prev, value])}
                onRemove={(index) => setDenyList((prev) => prev.filter((_, i) => i !== index))}
              />

              {/* Note about defaults */}
              <Text size="T200" priority="300">
                Tip: The default ACL allows all servers (allow: [&quot;*&quot;], deny: []). Adding
                &quot;*&quot; to the allow list permits all servers not explicitly denied.
              </Text>
            </Box>
          </PageContent>
        </Scroll>
      </Box>

      {/* #4 Confirmation dialog — surfaces the empty-allow (#1) and self-ban (#2)
          warnings and keeps a safe save one extra click. */}
      {prompt && (
        <Overlay open backdrop={<OverlayBackdrop />}>
          <OverlayCenter>
            <FocusTrap
              focusTrapOptions={{
                initialFocus: false,
                onDeactivate: () => setPrompt(false),
                clickOutsideDeactivates: true,
                escapeDeactivates: stopPropagation,
              }}
            >
              <Dialog
                variant="Surface"
                aria-labelledby="server-acl-confirm-title"
                style={modalStyle}
              >
                <Header
                  style={{
                    padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
                    borderBottomWidth: config.borderWidth.B300,
                  }}
                  variant="Surface"
                  size="500"
                >
                  <Box grow="Yes">
                    <Text as="h2" size="H4" id="server-acl-confirm-title">
                      Apply Server ACL
                    </Text>
                  </Box>
                  <IconButton
                    size="300"
                    onClick={() => setPrompt(false)}
                    radii="300"
                    aria-label="Cancel"
                  >
                    <Icon src={Icons.Cross} />
                  </IconButton>
                </Header>
                <Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
                  <Box direction="Column" gap="200">
                    <Text priority="400">
                      Server ACL changes take effect immediately and control which servers can
                      participate in this room. This cannot be undone by other servers once they are
                      removed.
                    </Text>
                    {emptyAllow && (
                      <Text size="T300" style={{ color: color.Critical.Main }}>
                        The allow list is empty — this would deny every server and partition the
                        room from all federation permanently.
                      </Text>
                    )}
                    {!emptyAllow && selfBanned && (
                      <Text size="T300" style={{ color: color.Warning.Main }}>
                        Warning: your own homeserver ({localDomain}) is not permitted by this ACL.
                        Applying it will remove your server from the room and you may lose the
                        ability to moderate it.
                      </Text>
                    )}
                  </Box>
                  <Button
                    type="submit"
                    variant={selfBanned ? 'Critical' : 'Primary'}
                    onClick={handleConfirmSave}
                    disabled={emptyAllow}
                  >
                    <Text size="B400">Apply ACL</Text>
                  </Button>
                </Box>
              </Dialog>
            </FocusTrap>
          </OverlayCenter>
        </Overlay>
      )}
    </Page>
  );
}
