import React, { useCallback, useRef, useState } from 'react';
import {
  Box,
  Button,
  Checkbox,
  Icon,
  IconButton,
  Icons,
  Input,
  Scroll,
  Spinner,
  Text,
  color,
  config,
} from 'folds';
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 { 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';

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

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

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

// ── Validation ────────────────────────────────────────────────────────────────

/**
 * Validate a server name or wildcard pattern.
 * Allowed forms:
 *   - plain hostname / IP:  letters, digits, hyphens, dots
 *   - wildcard prefix:      *.example.com  (asterisk only at the very start)
 * The Matrix spec allows `*` on its own (match-all wildcard).
 */
function isValidServerPattern(value: string): boolean {
  if (value === '*') return true;
  // Strip leading wildcard
  const rest = value.startsWith('*.') ? value.slice(2) : value;
  // Must not be empty after stripping wildcard
  if (!rest) return false;
  // Remaining part: only letters, digits, dots, hyphens, colons (for IPv6/ports)
  return /^[A-Za-z0-9.:_-]+$/.test(rest);
}

// ── 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 server pattern. Use a hostname or *.example.com');
      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"
                  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();

  // 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 mx.sendStateEvent(room.roomId, StateEvent.RoomServerAcl as any, {
        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;

  // 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}
                onClick={() => save()}
                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>
              )}

              {/* 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>
    </Page>
  );
}
