import { useEffect, useMemo, useState } from 'react';
import './styles.css';

interface AuthInfo {
  authenticated: boolean;
  user?: string;
  email?: string;
  groups?: string[];
}

interface BackupRecord {
  name: string;
  snapshot_name?: string;
  created?: string;
  state?: string;
  url?: string;
  size?: string;
  latest?: boolean;
}

interface BackupListResponse {
  namespace: string;
  pvc: string;
  volume: string;
  backups: BackupRecord[];
}

interface PVCInventory {
  namespace: string;
  pvc: string;
  volume?: string;
  phase?: string;
  storage_class?: string;
  capacity?: string;
  access_modes?: string[];
  driver?: string;
  last_backup_at?: string;
  last_backup_age_hours?: number;
  backup_count: number;
  completed_backups: number;
  active_backups: number;
  last_job_name?: string;
  last_job_state?: string;
  last_job_started_at?: string;
  last_job_progress_pct: number;
  last_backup_size_bytes?: number;
  total_backup_size_bytes?: number;
  healthy: boolean;
  health_reason?: string;
  error?: string;
}

interface NamespaceInventory {
  name: string;
  pvcs: PVCInventory[];
}

interface InventoryResponse {
  generated_at: string;
  namespaces: NamespaceInventory[];
}

interface BackupPolicy {
  id: string;
  namespace: string;
  pvc?: string;
  interval_hours: number;
  enabled: boolean;
  created_at?: string;
  updated_at?: string;
}

interface BackupPolicyListResponse {
  policies: BackupPolicy[];
}

interface B2BucketUsage {
  name: string;
  object_count: number;
  total_bytes: number;
  recent_objects_24h: number;
  recent_bytes_24h: number;
  last_modified_at?: string;
}

interface B2UsageResponse {
  enabled: boolean;
  available: boolean;
  endpoint?: string;
  region?: string;
  scanned_at?: string;
  scan_duration_ms?: number;
  total_objects: number;
  total_bytes: number;
  recent_objects_24h: number;
  recent_bytes_24h: number;
  buckets?: B2BucketUsage[];
  error?: string;
}

type RestoreSelection =
  | { kind: 'none' }
  | { kind: 'pvc'; namespace: string; pvc: string; volume: string; backups: BackupRecord[] }
  | { kind: 'namespace'; namespace: string };

const EMPTY_B2: B2UsageResponse = {
  enabled: false,
  available: false,
  total_objects: 0,
  total_bytes: 0,
  recent_objects_24h: 0,
  recent_bytes_24h: 0,
  buckets: []
};

async function fetchJSON<T>(input: string, init?: RequestInit): Promise<T> {
  const response = await fetch(input, init);
  const text = await response.text();
  let payload: unknown = {};
  if (text.trim() !== '') {
    try {
      payload = JSON.parse(text);
    } catch {
      payload = { error: text };
    }
  }
  if (!response.ok) {
    const message = typeof payload === 'object' && payload !== null && 'error' in payload
      ? String((payload as { error: unknown }).error)
      : `${response.status} ${response.statusText}`;
    throw new Error(message);
  }
  return payload as T;
}

function formatBytes(value?: number): string {
  if (!value || value <= 0) {
    return '0 B';
  }
  const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
  let size = value;
  let unit = 0;
  while (size >= 1024 && unit < units.length - 1) {
    size /= 1024;
    unit += 1;
  }
  return `${size.toFixed(unit === 0 ? 0 : 2)} ${units[unit]}`;
}

function formatTimestamp(value?: string): string {
  if (!value) {
    return 'n/a';
  }
  const parsed = new Date(value);
  if (Number.isNaN(parsed.valueOf())) {
    return value;
  }
  return parsed.toLocaleString();
}

function formatLabel(value?: string): string {
  if (!value) {
    return 'Needs attention';
  }
  return value.replace(/_/g, ' ');
}

function progressChipClass(state?: string): 'good' | 'warn' | 'bad' {
  switch ((state || '').toLowerCase()) {
    case 'completed':
      return 'good';
    case 'failed':
      return 'bad';
    default:
      return 'warn';
  }
}

function suggestTargetPVCName(sourcePVC: string): string {
  const now = new Date();
  const pad = (item: number) => String(item).padStart(2, '0');
  const stamp = [
    now.getUTCFullYear(),
    pad(now.getUTCMonth() + 1),
    pad(now.getUTCDate()),
    pad(now.getUTCHours()),
    pad(now.getUTCMinutes())
  ].join('');
  return (`restore-${sourcePVC}-${stamp}`)
    .toLowerCase()
    .replace(/[^a-z0-9-]/g, '-')
    .replace(/-+/g, '-')
    .replace(/^-|-$/g, '')
    .slice(0, 63)
    .replace(/-+$/g, '');
}

function suggestNamespacePrefix(): string {
  const now = new Date();
  const pad = (item: number) => String(item).padStart(2, '0');
  const stamp = [
    now.getUTCFullYear(),
    pad(now.getUTCMonth() + 1),
    pad(now.getUTCDate()),
    pad(now.getUTCHours()),
    pad(now.getUTCMinutes())
  ].join('');
  return `restore-${stamp}-`;
}

function App() {
  const [auth, setAuth] = useState<AuthInfo | null>(null);
  const [authError, setAuthError] = useState<string>('');

  const [inventory, setInventory] = useState<InventoryResponse | null>(null);
  const [inventoryError, setInventoryError] = useState<string>('');

  const [policies, setPolicies] = useState<BackupPolicy[]>([]);
  const [policyError, setPolicyError] = useState<string>('');

  const [b2Usage, setB2Usage] = useState<B2UsageResponse>(EMPTY_B2);
  const [b2Error, setB2Error] = useState<string>('');
  const [b2Refreshing, setB2Refreshing] = useState<boolean>(false);

  const [selection, setSelection] = useState<RestoreSelection>({ kind: 'none' });
  const [restoreNamespace, setRestoreNamespace] = useState<string>('');
  const [restorePVC, setRestorePVC] = useState<string>('');
  const [restoreBackupURL, setRestoreBackupURL] = useState<string>('');

  const [namespaceRestoreTarget, setNamespaceRestoreTarget] = useState<string>('');
  const [namespaceRestorePrefix, setNamespaceRestorePrefix] = useState<string>(suggestNamespacePrefix());
  const [namespaceRestoreSnapshot, setNamespaceRestoreSnapshot] = useState<string>('');

  const [policyNamespace, setPolicyNamespace] = useState<string>('');
  const [policyPVC, setPolicyPVC] = useState<string>('');
  const [policyIntervalHours, setPolicyIntervalHours] = useState<number>(24);
  const [policyEnabled, setPolicyEnabled] = useState<boolean>(true);

  const [lastAction, setLastAction] = useState<string>('No action yet.');
  const [busy, setBusy] = useState<boolean>(false);
  const activeBackupCount = useMemo(() => {
    if (!inventory) {
      return 0;
    }
    let total = 0;
    for (const namespace of inventory.namespaces) {
      for (const pvc of namespace.pvcs) {
        total += pvc.active_backups || 0;
      }
    }
    return total;
  }, [inventory]);

  const namespaceOptions = useMemo(() => {
    if (!inventory) {
      return [] as string[];
    }
    return inventory.namespaces.map((item) => item.name);
  }, [inventory]);

  const writeAction = (payload: unknown): void => {
    if (typeof payload === 'string') {
      setLastAction(payload);
      return;
    }
    setLastAction(JSON.stringify(payload, null, 2));
  };

  const loadWhoAmI = async (): Promise<void> => {
    try {
      const who = await fetchJSON<AuthInfo>('/v1/whoami');
      setAuth(who);
      setAuthError('');
    } catch (error) {
      setAuth(null);
      setAuthError(error instanceof Error ? error.message : 'failed to load auth');
    }
  };

  const loadInventory = async (): Promise<void> => {
    try {
      const payload = await fetchJSON<InventoryResponse>('/v1/inventory');
      setInventory(payload);
      setInventoryError('');
      if (!policyNamespace && payload.namespaces.length > 0) {
        setPolicyNamespace(payload.namespaces[0].name);
      }
    } catch (error) {
      setInventory(null);
      setInventoryError(error instanceof Error ? error.message : 'failed to load inventory');
    }
  };

  const loadPolicies = async (): Promise<void> => {
    try {
      const payload = await fetchJSON<BackupPolicyListResponse>('/v1/policies');
      setPolicies(payload.policies || []);
      setPolicyError('');
    } catch (error) {
      setPolicies([]);
      setPolicyError(error instanceof Error ? error.message : 'failed to load policies');
    }
  };

  const loadB2Usage = async (forceRefresh = false): Promise<void> => {
    try {
      const payload = await fetchJSON<B2UsageResponse>(forceRefresh ? '/v1/b2?refresh=1' : '/v1/b2');
      setB2Usage(payload);
      setB2Error('');
    } catch (error) {
      setB2Usage(EMPTY_B2);
      setB2Error(error instanceof Error ? error.message : 'failed to load B2 usage');
    }
  };

  const refreshB2Usage = async (): Promise<void> => {
    setB2Refreshing(true);
    try {
      await loadB2Usage(true);
    } finally {
      setB2Refreshing(false);
    }
  };

  const refreshAll = async (): Promise<void> => {
    setBusy(true);
    try {
      await Promise.all([loadWhoAmI(), loadInventory(), loadPolicies(), loadB2Usage()]);
    } finally {
      setBusy(false);
    }
  };

  useEffect(() => {
    void refreshAll();
  }, []);

  useEffect(() => {
    if (activeBackupCount <= 0) {
      return undefined;
    }
    const handle = window.setInterval(() => {
      void loadInventory();
    }, 8000);
    return () => {
      window.clearInterval(handle);
    };
  }, [activeBackupCount]);

  const triggerBackup = async (namespace: string, pvc: string): Promise<void> => {
    setBusy(true);
    try {
      const payload = await fetchJSON<unknown>('/v1/backup', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ namespace, pvc, dry_run: false })
      });
      writeAction(payload);
      await Promise.all([loadInventory(), loadB2Usage()]);
    } catch (error) {
      writeAction({ error: error instanceof Error ? error.message : 'backup request failed', namespace, pvc });
    } finally {
      setBusy(false);
    }
  };

  const triggerNamespaceBackup = async (namespace: string): Promise<void> => {
    setBusy(true);
    try {
      const payload = await fetchJSON<unknown>('/v1/backup/namespace', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ namespace, dry_run: false })
      });
      writeAction(payload);
      await Promise.all([loadInventory(), loadB2Usage()]);
    } catch (error) {
      writeAction({ error: error instanceof Error ? error.message : 'namespace backup failed', namespace });
    } finally {
      setBusy(false);
    }
  };

  const openPVCSelection = async (namespace: string, pvc: string): Promise<void> => {
    setBusy(true);
    try {
      const payload = await fetchJSON<BackupListResponse>(`/v1/backups?namespace=${encodeURIComponent(namespace)}&pvc=${encodeURIComponent(pvc)}`);
      const completed = payload.backups.filter((item) => item.state === 'Completed' && item.url);
      setSelection({ kind: 'pvc', namespace, pvc, volume: payload.volume, backups: completed });
      setRestoreNamespace(namespace);
      setRestorePVC(suggestTargetPVCName(pvc));
      setRestoreBackupURL(completed.length > 0 ? String(completed[0].url) : '');
      writeAction(payload);
    } catch (error) {
      writeAction({ error: error instanceof Error ? error.message : 'failed to load backups', namespace, pvc });
    } finally {
      setBusy(false);
    }
  };

  const openNamespaceSelection = (namespace: string): void => {
    setSelection({ kind: 'namespace', namespace });
    setNamespaceRestoreTarget(namespace);
    setNamespaceRestorePrefix(suggestNamespacePrefix());
    setNamespaceRestoreSnapshot('');
    writeAction(`Namespace restore planner loaded for ${namespace}.`);
  };

  const runPVCRestore = async (dryRun: boolean): Promise<void> => {
    if (selection.kind !== 'pvc') {
      return;
    }
    setBusy(true);
    try {
      const payload = await fetchJSON<unknown>('/v1/restores', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          namespace: selection.namespace,
          pvc: selection.pvc,
          backup_url: restoreBackupURL,
          target_namespace: restoreNamespace,
          target_pvc: restorePVC,
          dry_run: dryRun
        })
      });
      writeAction(payload);
      await Promise.all([loadInventory(), loadB2Usage()]);
    } catch (error) {
      writeAction({
        error: error instanceof Error ? error.message : 'restore failed',
        namespace: selection.namespace,
        pvc: selection.pvc,
        target_namespace: restoreNamespace,
        target_pvc: restorePVC,
        dry_run: dryRun
      });
    } finally {
      setBusy(false);
    }
  };

  const runNamespaceRestore = async (dryRun: boolean): Promise<void> => {
    if (selection.kind !== 'namespace') {
      return;
    }
    setBusy(true);
    try {
      const payload = await fetchJSON<unknown>('/v1/restores/namespace', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          namespace: selection.namespace,
          target_namespace: namespaceRestoreTarget,
          target_prefix: namespaceRestorePrefix,
          snapshot: namespaceRestoreSnapshot,
          dry_run: dryRun
        })
      });
      writeAction(payload);
      await Promise.all([loadInventory(), loadB2Usage()]);
    } catch (error) {
      writeAction({
        error: error instanceof Error ? error.message : 'namespace restore failed',
        namespace: selection.namespace,
        target_namespace: namespaceRestoreTarget,
        target_prefix: namespaceRestorePrefix,
        dry_run: dryRun
      });
    } finally {
      setBusy(false);
    }
  };

  const savePolicy = async (): Promise<void> => {
    setBusy(true);
    try {
      const payload = await fetchJSON<unknown>('/v1/policies', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          namespace: policyNamespace,
          pvc: policyPVC,
          interval_hours: policyIntervalHours,
          enabled: policyEnabled
        })
      });
      writeAction(payload);
      await Promise.all([loadPolicies(), loadInventory()]);
    } catch (error) {
      writeAction({ error: error instanceof Error ? error.message : 'policy save failed' });
    } finally {
      setBusy(false);
    }
  };

  const deletePolicy = async (policyID: string): Promise<void> => {
    setBusy(true);
    try {
      const payload = await fetchJSON<unknown>(`/v1/policies/${encodeURIComponent(policyID)}`, {
        method: 'DELETE'
      });
      writeAction(payload);
      await Promise.all([loadPolicies(), loadInventory()]);
    } catch (error) {
      writeAction({ error: error instanceof Error ? error.message : 'policy delete failed', policy_id: policyID });
    } finally {
      setBusy(false);
    }
  };

  const authLabel = auth
    ? `${auth.user || auth.email || 'authenticated'} (${(auth.groups || []).join(', ') || 'no groups'})`
    : authError || 'anonymous';

  return (
    <div className="app-shell">
      <header className="topbar">
        <div>
          <h1>Soteria Backup Console</h1>
          <p className="subtle">Dark-mode React UI for backup drills, policy control, and B2 consumption visibility.</p>
        </div>
        <div className="toolbar">
          <span className={`chip ${auth ? 'good' : 'warn'}`}>{authLabel}</span>
          {activeBackupCount > 0 && (
            <span className="chip warn">
              {activeBackupCount} backup job{activeBackupCount === 1 ? '' : 's'} active
            </span>
          )}
          <button type="button" className="secondary" onClick={() => void refreshAll()} disabled={busy}>
            {busy ? 'Refreshing...' : 'Refresh'}
          </button>
        </div>
      </header>

      <main className="layout">
        <section className="panel scroll-panel">
          <div className="panel-header">
            <h2>PVC Inventory</h2>
            <span className="subtle">{inventory?.generated_at ? `Updated ${formatTimestamp(inventory.generated_at)}` : 'No inventory yet'}</span>
          </div>
          {inventoryError && <p className="error">{inventoryError}</p>}
          {!inventory && !inventoryError && <p className="subtle">Loading inventory...</p>}
          {inventory?.namespaces.map((namespace) => (
            <article key={namespace.name} className="namespace-block">
              <div className="namespace-row">
                <h3>{namespace.name}</h3>
                <div className="actions">
                  <button type="button" className="secondary" onClick={() => void triggerNamespaceBackup(namespace.name)} disabled={busy}>
                    Backup namespace
                  </button>
                  <button type="button" className="secondary" onClick={() => openNamespaceSelection(namespace.name)}>
                    Restore namespace
                  </button>
                </div>
              </div>
              <div className="pvc-grid">
                {namespace.pvcs.map((pvc) => {
                  const healthClass = pvc.healthy ? 'good' : (pvc.health_reason === 'in_progress' ? 'warn' : 'bad');
                  const healthLabel = pvc.healthy ? 'Healthy' : formatLabel(pvc.health_reason);
                  const progressPct = Math.max(0, Math.min(100, Number(pvc.last_job_progress_pct || 0)));
                  const progressClass = progressChipClass(pvc.last_job_state);
                  const showProgress = Boolean(pvc.last_job_name) || (pvc.active_backups || 0) > 0;

                  return (
                    <article key={`${pvc.namespace}/${pvc.pvc}`} className="pvc-card">
                      <div className="pvc-title-row">
                        <div>
                          <h4>{pvc.pvc}</h4>
                          <p className="subtle tiny">{pvc.volume || 'unknown volume'} | {pvc.storage_class || 'no class'} | {pvc.capacity || 'unknown size'}</p>
                        </div>
                        <span className={`chip ${healthClass}`}>
                          {healthLabel}
                        </span>
                      </div>
                      <p className="subtle tiny">
                        Last backup: {pvc.last_backup_at ? `${formatTimestamp(pvc.last_backup_at)} (${(pvc.last_backup_age_hours || 0).toFixed(1)}h ago)` : 'never'}
                      </p>
                      <p className="subtle tiny">
                        Backups: {pvc.completed_backups}/{pvc.backup_count} completed | Latest size: {formatBytes(pvc.last_backup_size_bytes)} | Total stored: {formatBytes(pvc.total_backup_size_bytes)}
                      </p>
                      {showProgress && (
                        <div className="backup-progress">
                          <div className="progress-header">
                            <p className="subtle tiny">
                              Job: {pvc.last_job_name || 'n/a'}
                              {pvc.last_job_started_at ? ` | Started ${formatTimestamp(pvc.last_job_started_at)}` : ''}
                            </p>
                            <span className={`chip ${progressClass}`}>{pvc.last_job_state || 'Unknown'}</span>
                          </div>
                          <div className="progress-track" role="progressbar" aria-valuemin={0} aria-valuemax={100} aria-valuenow={progressPct}>
                            <div className={`progress-fill ${progressClass} ${pvc.active_backups > 0 ? 'active' : ''}`} style={{ width: `${progressPct}%` }} />
                          </div>
                          <p className="subtle tiny">Progress {progressPct}% | Active jobs: {pvc.active_backups || 0}</p>
                        </div>
                      )}
                      {pvc.error && <p className="error tiny">{pvc.error}</p>}
                      <div className="actions">
                        <button type="button" onClick={() => void triggerBackup(pvc.namespace, pvc.pvc)} disabled={busy}>
                          Backup now
                        </button>
                        <button type="button" className="secondary" onClick={() => void openPVCSelection(pvc.namespace, pvc.pvc)}>
                          Restore
                        </button>
                      </div>
                    </article>
                  );
                })}
              </div>
            </article>
          ))}
        </section>

        <section className="column">
          <section className="panel">
            <h2>Restore Planner</h2>
            <p className="subtle tiny">Inventory restore buttons preload this panel. This is where restore dry-runs and execution happen.</p>
            {selection.kind === 'none' && <p className="subtle">Choose Restore on a PVC or namespace to begin.</p>}

            {selection.kind === 'pvc' && (
              <div className="stack">
                <p className="subtle tiny"><strong>Source:</strong> {selection.namespace}/{selection.pvc} ({selection.volume})</p>
                <label>
                  Backup snapshot
                  <select value={restoreBackupURL} onChange={(event) => setRestoreBackupURL(event.target.value)}>
                    {selection.backups.length === 0 && <option value="">No completed backups</option>}
                    {selection.backups.map((item) => (
                      <option key={item.url || item.name} value={item.url || ''}>
                        {item.name} | {item.created || 'unknown time'} | {item.size || 'size n/a'}
                      </option>
                    ))}
                  </select>
                </label>
                <label>
                  Target namespace
                  <select value={restoreNamespace} onChange={(event) => setRestoreNamespace(event.target.value)}>
                    {namespaceOptions.map((item) => <option key={item} value={item}>{item}</option>)}
                  </select>
                </label>
                <label>
                  Target PVC
                  <input value={restorePVC} onChange={(event) => setRestorePVC(event.target.value)} />
                </label>
                <div className="actions">
                  <button type="button" onClick={() => void runPVCRestore(false)} disabled={busy || !restoreBackupURL}>
                    Create restore PVC
                  </button>
                  <button type="button" className="secondary" onClick={() => void runPVCRestore(true)} disabled={busy || !restoreBackupURL}>
                    Dry run
                  </button>
                </div>
              </div>
            )}

            {selection.kind === 'namespace' && (
              <div className="stack">
                <p className="subtle tiny"><strong>Source namespace:</strong> {selection.namespace}</p>
                <label>
                  Target namespace
                  <select value={namespaceRestoreTarget} onChange={(event) => setNamespaceRestoreTarget(event.target.value)}>
                    {namespaceOptions.map((item) => <option key={item} value={item}>{item}</option>)}
                  </select>
                </label>
                <label>
                  Target PVC prefix
                  <input value={namespaceRestorePrefix} onChange={(event) => setNamespaceRestorePrefix(event.target.value)} />
                </label>
                <label>
                  Snapshot hint (optional)
                  <input value={namespaceRestoreSnapshot} onChange={(event) => setNamespaceRestoreSnapshot(event.target.value)} placeholder="blank = latest completed" />
                </label>
                <div className="actions">
                  <button type="button" onClick={() => void runNamespaceRestore(false)} disabled={busy}>
                    Create restore PVCs
                  </button>
                  <button type="button" className="secondary" onClick={() => void runNamespaceRestore(true)} disabled={busy}>
                    Dry run
                  </button>
                </div>
              </div>
            )}
          </section>

          <section className="panel action-panel">
            <h2>Last Action</h2>
            <pre>{lastAction}</pre>
          </section>
        </section>

        <section className="column">
          <section className="panel">
            <div className="panel-header">
              <h2>B2 Consumption</h2>
              <button type="button" className="secondary" onClick={() => void refreshB2Usage()} disabled={b2Refreshing}>
                {b2Refreshing ? 'Refreshing...' : 'Refresh B2'}
              </button>
            </div>
            {b2Error && <p className="error">{b2Error}</p>}
            {!b2Error && !b2Usage.enabled && <p className="subtle">B2 monitoring is disabled in Soteria config.</p>}
            {!b2Error && b2Usage.enabled && !b2Usage.available && <p className="error">{b2Usage.error || 'B2 usage currently unavailable.'}</p>}
            {b2Usage.enabled && (
              <div className="stack">
                <p className="subtle tiny">Endpoint: {b2Usage.endpoint || 'n/a'} | Region: {b2Usage.region || 'n/a'}</p>
                <p className="subtle tiny">Last scan: {formatTimestamp(b2Usage.scanned_at)} | Duration: {b2Usage.scan_duration_ms || 0}ms</p>
                <div className="stat-grid">
                  <div className="stat">
                    <span className="label">Stored bytes</span>
                    <strong>{formatBytes(b2Usage.total_bytes)}</strong>
                  </div>
                  <div className="stat">
                    <span className="label">Objects</span>
                    <strong>{b2Usage.total_objects}</strong>
                  </div>
                  <div className="stat">
                    <span className="label">Recent bytes (24h)</span>
                    <strong>{formatBytes(b2Usage.recent_bytes_24h)}</strong>
                  </div>
                  <div className="stat">
                    <span className="label">Recent objects (24h)</span>
                    <strong>{b2Usage.recent_objects_24h}</strong>
                  </div>
                </div>
                <p className="subtle tiny">Recent 24h values are object-change bandwidth proxy from bucket scans. B2 egress billing totals are not exposed by S3 object listing.</p>
                <div className="bucket-table-wrap">
                  <table>
                    <thead>
                      <tr>
                        <th>Bucket</th>
                        <th>Objects</th>
                        <th>Stored</th>
                        <th>Recent 24h</th>
                      </tr>
                    </thead>
                    <tbody>
                      {(b2Usage.buckets || []).map((bucket) => (
                        <tr key={bucket.name}>
                          <td>
                            <div>{bucket.name}</div>
                            <div className="subtle tiny">Last object: {formatTimestamp(bucket.last_modified_at)}</div>
                          </td>
                          <td>{bucket.object_count}</td>
                          <td>{formatBytes(bucket.total_bytes)}</td>
                          <td>{formatBytes(bucket.recent_bytes_24h)}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              </div>
            )}
          </section>

          <section className="panel scroll-panel">
            <h2>Backup Policies</h2>
            <div className="stack">
              <label>
                Namespace
                <select value={policyNamespace} onChange={(event) => setPolicyNamespace(event.target.value)}>
                  {namespaceOptions.map((item) => <option key={item} value={item}>{item}</option>)}
                </select>
              </label>
              <label>
                PVC (optional)
                <input value={policyPVC} onChange={(event) => setPolicyPVC(event.target.value)} placeholder="blank means all PVCs in namespace" />
              </label>
              <label>
                Interval hours
                <input
                  type="number"
                  min={1}
                  value={policyIntervalHours}
                  onChange={(event) => setPolicyIntervalHours(Math.max(1, Number(event.target.value || 1)))}
                />
              </label>
              <label className="checkbox-row">
                <input type="checkbox" checked={policyEnabled} onChange={(event) => setPolicyEnabled(event.target.checked)} />
                Enabled
              </label>
              <button type="button" onClick={() => void savePolicy()} disabled={busy || !policyNamespace}>Save policy</button>
            </div>

            {policyError && <p className="error">{policyError}</p>}
            {!policyError && policies.length === 0 && <p className="subtle">No policies yet.</p>}
            <div className="policy-list">
              {policies.map((policy) => (
                <article key={policy.id} className="policy-item">
                  <div className="policy-head">
                    <strong>{policy.namespace}/{policy.pvc || '*'}</strong>
                    <span className={`chip ${policy.enabled ? 'good' : 'bad'}`}>{policy.enabled ? 'Enabled' : 'Disabled'}</span>
                  </div>
                  <p className="subtle tiny">Every {policy.interval_hours}h | Updated {formatTimestamp(policy.updated_at || policy.created_at)}</p>
                  <div className="actions">
                    <button
                      type="button"
                      className="secondary"
                      onClick={() => {
                        setPolicyNamespace(policy.namespace);
                        setPolicyPVC(policy.pvc || '');
                        setPolicyIntervalHours(policy.interval_hours);
                        setPolicyEnabled(policy.enabled);
                      }}
                    >
                      Load
                    </button>
                    <button type="button" className="secondary" onClick={() => void deletePolicy(policy.id)} disabled={busy}>
                      Delete
                    </button>
                  </div>
                </article>
              ))}
            </div>
          </section>
        </section>
      </main>
    </div>
  );
}

export default App;
