import React, { FormEventHandler, useEffect, useId, useRef, useState } from 'react';
import {
  Box,
  Button,
  Icon,
  IconButton,
  Icons,
  Menu,
  MenuItem,
  PopOut,
  RectCords,
  Spinner,
  Switch,
  Text,
  color,
  config,
} from 'folds';
import FocusTrap from 'focus-trap-react';
import { useTranslation } from 'react-i18next';
import { SectionLabel } from '../styles.css';
import { useProxy } from '../../../hooks/useProxy';
import { isAndroidPlatform } from '../../../utils/capacitor';
import { stopPropagation } from '../../../utils/keyboard';
import type { ProxyStatus, ProxyType } from '../../../plugins/proxy';
import * as css from './network.css';

const TYPE_OPTIONS: ProxyType[] = ['socks5', 'http'];

// A bare host is `letters/digits/dots/hyphens/colon-for-ipv6` with no scheme.
// We explicitly forbid `http://` / `https://` prefixes: the WebView proxy-auth
// mechanism only works for a plaintext-HTTP proxy inbound, an HTTPS proxy
// hard-fails ERR_PROXY_AUTH_UNSUPPORTED (proxy_support.md §15 #2). So the form
// accepts only a bare host and a scheme derived from the type selector.
const SCHEME_RE = /:\/\//;

type ProxyFormProps = {
  // The live homeserver base URL (`https://matrix.vojo.chat`). Used to scope
  // the override to that host (reverse-bypass). Omit pre-login when the
  // homeserver isn't resolved yet — the override then routes all traffic.
  homeserverBaseUrl?: string;
  // Compact = the pre-login overlay (which has its own header), so the section
  // label is dropped.
  compact?: boolean;
};

export function ProxyForm({ homeserverBaseUrl, compact }: ProxyFormProps) {
  const { t, i18n } = useTranslation();
  const { state, saveConfig, setEnabled, clearConfig, probe } = useProxy();
  const fid = useId();
  const locale = i18n.language || 'en';

  // Tick once a second while enabled so the uptime / "checked …" rows stay live.
  const [now, setNow] = useState(() => Date.now());
  useEffect(() => {
    if (!state.enabled) return undefined;
    const id = window.setInterval(() => setNow(Date.now()), 1000);
    return () => window.clearInterval(id);
  }, [state.enabled]);

  const [type, setType] = useState<ProxyType>('socks5');
  const [typeMenu, setTypeMenu] = useState<RectCords>();
  const [host, setHost] = useState('');
  const [port, setPort] = useState('');
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [passwordTouched, setPasswordTouched] = useState(false);
  const [showPassword, setShowPassword] = useState(false);
  const [error, setError] = useState<string>();
  const [busy, setBusy] = useState(false);

  // Prefill the non-secret fields once the native state has hydrated. The
  // password is never read back — it stays blank with an "unchanged" hint.
  const initializedRef = useRef(false);
  useEffect(() => {
    if (initializedRef.current) return;
    if (!state.configured) return;
    initializedRef.current = true;
    if (state.type) setType(state.type);
    if (state.host) setHost(state.host);
    if (typeof state.port === 'number') setPort(String(state.port));
    if (state.username) setUsername(state.username);
  }, [state]);

  const validate = (): { host: string; port: number } | undefined => {
    const trimmedHost = host.trim();
    if (!trimmedHost) {
      setError(t('Settings.network.error_host_required'));
      return undefined;
    }
    if (SCHEME_RE.test(trimmedHost) || /\s/.test(trimmedHost)) {
      setError(t('Settings.network.error_host_scheme'));
      return undefined;
    }
    const portNum = Number.parseInt(port, 10);
    if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) {
      setError(t('Settings.network.error_port_invalid'));
      return undefined;
    }
    setError(undefined);
    return { host: trimmedHost, port: portNum };
  };

  const persist = async (): Promise<boolean> => {
    const valid = validate();
    if (!valid) return false;
    try {
      await saveConfig({
        type,
        host: valid.host,
        port: valid.port,
        username: username.trim() || undefined,
        // Omit the password when the user didn't touch it: the native store
        // keeps the existing encrypted value. An empty username clears auth.
        password: passwordTouched ? password : undefined,
      });
    } catch {
      // The native store rejected (e.g. Keystore failure). Surface it and KEEP
      // the typed password — clearing it here (the success path below) would
      // silently lose the secret the user just entered (F11).
      setError(t('Settings.network.error_save_failed'));
      return false;
    }
    setPasswordTouched(false);
    setPassword('');
    return true;
  };

  const handleSave: FormEventHandler<HTMLFormElement> = async (evt) => {
    evt.preventDefault();
    if (busy) return;
    setBusy(true);
    try {
      const ok = await persist();
      // Re-apply immediately if the proxy is already on, so edits take effect
      // without a manual off/on cycle.
      if (ok && state.enabled) {
        await setEnabled(true, homeserverBaseUrl);
      }
    } finally {
      setBusy(false);
    }
  };

  const handleToggle = async (next: boolean) => {
    if (busy) return;
    setBusy(true);
    try {
      if (next) {
        if (!(await persist())) return;
        await setEnabled(true, homeserverBaseUrl);
      } else {
        await setEnabled(false, homeserverBaseUrl);
      }
    } finally {
      setBusy(false);
    }
  };

  const handleRemove = async () => {
    if (busy) return;
    setBusy(true);
    try {
      await clearConfig();
      initializedRef.current = false;
      setHost('');
      setPort('');
      setUsername('');
      setPassword('');
      setPasswordTouched(false);
      setError(undefined);
    } finally {
      setBusy(false);
    }
  };

  // Pre-login (no homeserverBaseUrl) we can't probe reachability, so a native
  // 'connecting' would hang forever. Present it as a calm neutral "pending"
  // (on, will verify at sign-in) instead of a perpetual amber "Connecting…"
  // (F2/F18). Once logged in, the boot probe gives a real ok/error.
  const canProbe = Boolean(homeserverBaseUrl);
  const displayStatus: ProxyStatus | 'pending' =
    state.enabled && !canProbe && state.status === 'connecting' ? 'pending' : state.status;
  const statusLabel = ((): string => {
    switch (displayStatus) {
      case 'ok':
        return t('Settings.network.status_ok');
      case 'error':
        return t('Settings.network.status_error');
      case 'connecting':
        return t('Settings.network.status_connecting');
      case 'pending':
        return t('Settings.network.status_pending');
      default:
        return t('Settings.network.status_off');
    }
  })();

  const onPortChange = (e: React.ChangeEvent<HTMLInputElement>) =>
    setPort(e.target.value.replace(/[^\d]/g, '').slice(0, 5));

  const typeLabel = (v: ProxyType): string =>
    v === 'socks5' ? t('Settings.network.type_socks5') : t('Settings.network.type_http');

  // Locale-aware metadata formatters (no per-unit i18n keys needed — Intl
  // renders "123 ms" / "123 мс", "5 min" / "5 мин", "2 minutes ago" / …).
  const unit = (value: number, u: string): string =>
    new Intl.NumberFormat(locale, { style: 'unit', unit: u, unitDisplay: 'short' }).format(value);
  const fmtPing = (ms: number): string => unit(ms, 'millisecond');
  const fmtUptime = (sinceMs: number): string => {
    const secs = Math.max(0, Math.floor((now - sinceMs) / 1000));
    if (secs < 60) return unit(secs, 'second');
    const mins = Math.floor(secs / 60);
    if (mins < 60) return unit(mins, 'minute');
    return `${unit(Math.floor(mins / 60), 'hour')} ${unit(mins % 60, 'minute')}`;
  };
  const fmtChecked = (atMs: number): string => {
    const deltaS = Math.round((atMs - now) / 1000);
    const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
    return Math.abs(deltaS) < 60
      ? rtf.format(deltaS, 'second')
      : rtf.format(Math.round(deltaS / 60), 'minute');
  };

  const handleRecheck = async () => {
    if (!homeserverBaseUrl || busy) return;
    setBusy(true);
    try {
      await probe(homeserverBaseUrl);
    } finally {
      setBusy(false);
    }
  };

  // "Has anything actually changed since what's saved?" — drives the Save
  // button's enabled state so it isn't perpetually lit with nothing to persist.
  // The password counts as a change only once the user types into it
  // (passwordTouched), since the saved secret never round-trips to JS.
  const savedPort = typeof state.port === 'number' ? String(state.port) : '';
  const isDirty =
    passwordTouched ||
    type !== (state.type ?? 'socks5') ||
    host.trim() !== (state.host ?? '') ||
    port !== savedPort ||
    username.trim() !== (state.username ?? '');

  return (
    <Box direction="Column" gap="400">
      {/* Hero: icon + title + live status + the big toggle. */}
      <div className={css.HeroCard}>
        <div className={css.HeroIcon}>
          <Icon src={Icons.Server} size="300" />
        </div>
        <div className={css.HeroText}>
          <Text size="T400" truncate>
            {t('Settings.network.enabled_label')}
          </Text>
          <span className={css.StatusLine}>
            <span className={`${css.StatusDot} ${css.statusDotColor[displayStatus]}`} />
            <Text size="T200" priority="300" truncate>
              {statusLabel}
            </Text>
          </span>
        </div>
        {busy && <Spinner size="100" variant="Secondary" />}
        <Switch
          variant="Primary"
          value={state.enabled}
          onChange={handleToggle}
          disabled={!state.supported || busy}
        />
      </div>

      {!state.supported ? (
        <div className={css.NoticeCard}>
          <span className={css.InfoIcon}>
            <Icon size="300" src={Icons.Info} />
          </span>
          <Text size="T200">
            {isAndroidPlatform()
              ? t('Settings.network.unsupported')
              : t('Settings.network.unavailable_platform')}
          </Text>
        </div>
      ) : (
        <>
          {state.enabled && (
            <Box direction="Column" gap="200">
              {!compact && (
                <Text className={SectionLabel}>{t('Settings.network.section_status')}</Text>
              )}
              <div className={css.Panel}>
                <div className={css.FieldRow}>
                  <span className={css.FieldLabel}>{t('Settings.network.ping_label')}</span>
                  <span className={css.MetaValue}>
                    {state.latencyMs != null ? fmtPing(state.latencyMs) : '—'}
                  </span>
                </div>
                <div className={css.FieldRow}>
                  <span className={css.FieldLabel}>{t('Settings.network.uptime_label')}</span>
                  <span className={css.MetaValue}>
                    {state.enabledSince ? fmtUptime(state.enabledSince) : '—'}
                  </span>
                </div>
                <div className={css.FieldRow}>
                  <span className={css.FieldLabel}>{t('Settings.network.checked_label')}</span>
                  <span className={css.MetaValue}>
                    {state.checkedAt ? fmtChecked(state.checkedAt) : '—'}
                    {homeserverBaseUrl && (
                      <IconButton
                        type="button"
                        size="300"
                        radii="300"
                        variant="SurfaceVariant"
                        onClick={handleRecheck}
                        disabled={busy}
                        aria-label={t('Settings.network.recheck')}
                      >
                        <Icon size="100" src={Icons.Reload} />
                      </IconButton>
                    )}
                  </span>
                </div>
                <div className={css.FieldRow}>
                  <span className={css.FieldLabel}>{t('Settings.network.scope_label')}</span>
                  <span className={css.MetaValue}>
                    {state.scopeHost ?? t('Settings.network.scope_all')}
                  </span>
                </div>
              </div>
              {state.nativeBlockedAt ? (
                <span className={css.WarnLine}>
                  <Icon size="100" src={Icons.Warning} style={{ flexShrink: 0 }} />
                  <Text size="T200">{t('Settings.network.native_blocked')}</Text>
                </span>
              ) : null}
            </Box>
          )}

          {/* Server section: type + address + optional auth, one grouped panel. */}
          <form onSubmit={handleSave}>
            <Box direction="Column" gap="200">
              {!compact && (
                <Text className={SectionLabel}>{t('Settings.network.section_proxy')}</Text>
              )}
              <div className={css.Panel}>
                <div className={css.FieldRow}>
                  <span className={css.FieldLabel}>{t('Settings.network.type_label')}</span>
                  <button
                    type="button"
                    className={css.SelectValue}
                    onClick={(e) => setTypeMenu(e.currentTarget.getBoundingClientRect())}
                    aria-haspopup="menu"
                  >
                    {typeLabel(type)}
                    <span className={css.SelectChevron}>
                      <Icon size="100" src={Icons.ChevronBottom} />
                    </span>
                  </button>
                  <PopOut
                    anchor={typeMenu}
                    offset={5}
                    position="Bottom"
                    align="End"
                    content={
                      <FocusTrap
                        focusTrapOptions={{
                          initialFocus: false,
                          onDeactivate: () => setTypeMenu(undefined),
                          clickOutsideDeactivates: true,
                          escapeDeactivates: stopPropagation,
                        }}
                      >
                        <Menu>
                          <Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
                            {TYPE_OPTIONS.map((opt) => (
                              <MenuItem
                                key={opt}
                                size="300"
                                variant="Surface"
                                radii="300"
                                aria-selected={type === opt}
                                onClick={() => {
                                  setType(opt);
                                  setTypeMenu(undefined);
                                }}
                              >
                                <Box grow="Yes" alignItems="Center" gap="200">
                                  <Text style={{ flexGrow: 1 }} size="T300">
                                    {typeLabel(opt)}
                                  </Text>
                                  {type === opt && <Icon size="100" src={Icons.Check} />}
                                </Box>
                              </MenuItem>
                            ))}
                          </Box>
                        </Menu>
                      </FocusTrap>
                    }
                  />
                </div>

                <div className={css.FieldRow}>
                  <label className={css.FieldLabel} htmlFor={`${fid}-host`}>
                    {t('Settings.network.host_label')}
                  </label>
                  <input
                    id={`${fid}-host`}
                    className={css.FieldInput}
                    value={host}
                    onChange={(e: React.ChangeEvent<HTMLInputElement>) => setHost(e.target.value)}
                    placeholder={t('Settings.network.host_placeholder')}
                    autoComplete="off"
                    autoCapitalize="off"
                    autoCorrect="off"
                    spellCheck={false}
                    inputMode="url"
                  />
                </div>

                <div className={css.FieldRow}>
                  <label className={css.FieldLabel} htmlFor={`${fid}-port`}>
                    {t('Settings.network.port_label')}
                  </label>
                  <input
                    id={`${fid}-port`}
                    className={css.FieldInput}
                    value={port}
                    onChange={onPortChange}
                    placeholder={t('Settings.network.port_placeholder')}
                    inputMode="numeric"
                  />
                </div>

                <div className={css.FieldRow}>
                  <label className={css.FieldLabel} htmlFor={`${fid}-user`}>
                    {t('Settings.network.username_label_short')}
                  </label>
                  <input
                    id={`${fid}-user`}
                    className={css.FieldInput}
                    value={username}
                    onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                      setUsername(e.target.value)
                    }
                    placeholder={t('Settings.network.optional')}
                    autoComplete="off"
                    autoCapitalize="off"
                    autoCorrect="off"
                    spellCheck={false}
                  />
                </div>

                <div className={css.FieldRow}>
                  <label className={css.FieldLabel} htmlFor={`${fid}-pass`}>
                    {t('Settings.network.password_label_short')}
                  </label>
                  <input
                    id={`${fid}-pass`}
                    className={css.FieldInput}
                    type={showPassword ? 'text' : 'password'}
                    value={password}
                    onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                      setPassword(e.target.value);
                      setPasswordTouched(true);
                    }}
                    placeholder={
                      state.hasAuth
                        ? t('Settings.network.password_placeholder_set')
                        : t('Settings.network.optional')
                    }
                    autoComplete="off"
                  />
                  <button
                    type="button"
                    className={css.EyeButton}
                    onClick={() => setShowPassword((v) => !v)}
                    aria-label={t('Settings.network.toggle_password')}
                  >
                    <Icon size="100" src={showPassword ? Icons.Eye : Icons.EyeBlind} />
                  </button>
                </div>
              </div>

              {error && (
                <Text size="T200" style={{ color: color.Critical.Main }}>
                  {error}
                </Text>
              )}

              {!state.reverseBypassSupported && (
                <Text size="T200" style={{ color: color.Warning.Main }}>
                  {t('Settings.network.reverse_bypass_unsupported')}
                </Text>
              )}

              <Button
                type="submit"
                variant="Primary"
                size="500"
                radii="400"
                fill="Solid"
                disabled={busy || !isDirty}
              >
                <Text size="B400">{t('Settings.network.save')}</Text>
              </Button>

              {state.configured && (
                <Button
                  type="button"
                  variant="Critical"
                  fill="None"
                  size="400"
                  radii="400"
                  onClick={handleRemove}
                  disabled={busy}
                  before={<Icon size="100" src={Icons.Delete} />}
                >
                  <Text size="B400">{t('Settings.network.remove')}</Text>
                </Button>
              )}
            </Box>
          </form>
        </>
      )}
    </Box>
  );
}
