'use client';

import { useCallback, useEffect, useRef, useState } from 'react';
import Link from 'next/link';
import { formatBytes, formatDate } from '@/lib/format';
import type { AdminUserItem } from '@/lib/admin/list';
import { SortHeader, downloadUrl, type SortOrder } from '../table-ui';

const PER_PAGE = 25;

function isBanned(u: AdminUserItem): boolean {
  return !!u.banned_until && new Date(u.banned_until).getTime() > Date.now();
}

type BulkResult = { ok: number; fail: number; skipped: number };

export function UsersTable({
  initialUsers,
  initialTotal,
  currentUserId,
}: {
  initialUsers: AdminUserItem[];
  initialTotal: number;
  currentUserId: string;
}) {
  const [users, setUsers] = useState<AdminUserItem[]>(initialUsers);
  const [total, setTotal] = useState(initialTotal);
  const [page, setPage] = useState(1);
  const [search, setSearch] = useState('');
  const [sort, setSort] = useState('created_at');
  const [order, setOrder] = useState<SortOrder>('desc');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [notice, setNotice] = useState<string | null>(null);
  const [selected, setSelected] = useState<Set<string>>(new Set());
  const [bulkBusy, setBulkBusy] = useState(false);
  const [bulkProgress, setBulkProgress] = useState<string | null>(null);

  const queryParams = useCallback(() => {
    const params = new URLSearchParams({
      page: String(page),
      perPage: String(PER_PAGE),
      sort,
      order,
    });
    if (search.trim()) params.set('search', search.trim());
    return params;
  }, [page, search, sort, order]);

  const load = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const res = await fetch(`/api/admin/users?${queryParams().toString()}`, {
        credentials: 'same-origin',
      });
      if (!res.ok) {
        const b = (await res.json().catch(() => ({}))) as { error?: string };
        throw new Error(b.error ?? `Request failed (${res.status})`);
      }
      const data = (await res.json()) as {
        users: AdminUserItem[];
        total: number;
      };
      setUsers(data.users);
      setTotal(data.total);
      setSelected(new Set());
    } catch (e) {
      setError((e as Error).message);
    } finally {
      setLoading(false);
    }
  }, [queryParams]);

  // The first page is rendered from server-supplied data (see the parent server
  // component), so skip the initial on-mount fetch — that on-mount request used
  // to race the SSR session-cookie refresh and intermittently 401.
  const didMount = useRef(false);
  useEffect(() => {
    if (!didMount.current) {
      didMount.current = true;
      return;
    }
    const t = setTimeout(load, 250);
    return () => clearTimeout(t);
  }, [load]);

  function onSort(col: string) {
    setPage(1);
    if (col === sort) {
      setOrder((o) => (o === 'asc' ? 'desc' : 'asc'));
    } else {
      setSort(col);
      setOrder('asc');
    }
  }

  function toggleRow(id: string) {
    setSelected((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }

  const pageIds = users.map((u) => u.id);
  const allSelected =
    pageIds.length > 0 && pageIds.every((id) => selected.has(id));
  function toggleAll() {
    setSelected((prev) => {
      if (pageIds.every((id) => prev.has(id))) return new Set();
      return new Set(pageIds);
    });
  }

  async function runBulk(
    label: string,
    perItem: (u: AdminUserItem) => Promise<'ok' | 'fail' | 'skip'>,
    confirmMsg?: string,
  ) {
    const targets = users.filter((u) => selected.has(u.id));
    if (targets.length === 0) return;
    if (confirmMsg && !window.confirm(confirmMsg)) return;
    setBulkBusy(true);
    setError(null);
    setNotice(null);
    const result: BulkResult = { ok: 0, fail: 0, skipped: 0 };
    for (let i = 0; i < targets.length; i++) {
      setBulkProgress(`${label}: ${i + 1}/${targets.length}…`);
      try {
        const r = await perItem(targets[i]);
        if (r === 'ok') result.ok++;
        else if (r === 'skip') result.skipped++;
        else result.fail++;
      } catch {
        result.fail++;
      }
    }
    setBulkProgress(null);
    setBulkBusy(false);
    const parts = [`${result.ok} ${label.toLowerCase()}`];
    if (result.skipped) parts.push(`${result.skipped} skipped (self)`);
    if (result.fail) parts.push(`${result.fail} failed`);
    setNotice(parts.join(', '));
    await load();
  }

  async function banOne(
    u: AdminUserItem,
    banned: boolean,
  ): Promise<'ok' | 'fail' | 'skip'> {
    if (u.id === currentUserId) return 'skip';
    const res = await fetch(`/api/admin/users/${u.id}/ban`, {
      method: 'POST',
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ banned }),
    });
    return res.ok ? 'ok' : 'fail';
  }

  async function deleteOne(
    u: AdminUserItem,
  ): Promise<'ok' | 'fail' | 'skip'> {
    if (u.id === currentUserId) return 'skip';
    const res = await fetch(`/api/admin/users/${u.id}`, {
      method: 'DELETE',
      credentials: 'same-origin',
    });
    return res.ok ? 'ok' : 'fail';
  }

  function exportCsv() {
    const params = new URLSearchParams({ sort, order });
    if (search.trim()) params.set('search', search.trim());
    downloadUrl(`/api/admin/users/export?${params.toString()}`);
  }

  const totalPages = Math.max(1, Math.ceil(total / PER_PAGE));
  const selectedCount = selected.size;

  return (
    <div>
      <h1>Users</h1>

      <div className="row" style={{ marginBottom: '1rem', flexWrap: 'wrap' }}>
        <input
          type="text"
          placeholder="Search by email…"
          value={search}
          onChange={(e) => {
            setPage(1);
            setSearch(e.target.value);
          }}
          style={{ maxWidth: 320 }}
        />
        <button className="secondary btn-sm" onClick={load} type="button">
          Refresh
        </button>
        <div className="toolbar-actions">
          <button
            className="secondary btn-sm"
            type="button"
            onClick={exportCsv}
          >
            Export CSV
          </button>
        </div>
      </div>

      {error && <p className="error">{error}</p>}
      {notice && <p className="success">{notice}</p>}

      {selectedCount > 0 && (
        <div className="bulk-bar">
          <span className="bulk-count">{selectedCount} selected</span>
          <button
            className="secondary btn-sm"
            type="button"
            disabled={bulkBusy}
            onClick={() => runBulk('Banned', (u) => banOne(u, true))}
          >
            Ban
          </button>
          <button
            className="secondary btn-sm"
            type="button"
            disabled={bulkBusy}
            onClick={() => runBulk('Unbanned', (u) => banOne(u, false))}
          >
            Unban
          </button>
          <button
            className="btn-danger btn-sm"
            type="button"
            disabled={bulkBusy}
            onClick={() =>
              runBulk(
                'Deleted',
                deleteOne,
                `Delete ${selectedCount} selected user(s)? This cannot be undone. Your own account is skipped.`,
              )
            }
          >
            Delete
          </button>
          <button
            className="secondary btn-sm"
            type="button"
            disabled={bulkBusy}
            onClick={() => setSelected(new Set())}
          >
            Clear
          </button>
          {bulkProgress && <span className="muted">{bulkProgress}</span>}
        </div>
      )}

      {loading ? (
        <p className="muted">Loading…</p>
      ) : users.length === 0 ? (
        <p className="muted">No users found.</p>
      ) : (
        <div className="admin-table-wrap">
          <table className="admin-table">
            <thead>
              <tr>
                <th className="col-check">
                  <input
                    type="checkbox"
                    aria-label="Select all on page"
                    checked={allSelected}
                    onChange={toggleAll}
                  />
                </th>
                <SortHeader
                  label="Email"
                  col="email"
                  sort={sort}
                  order={order}
                  onSort={onSort}
                />
                <SortHeader
                  label="Role"
                  col="role"
                  sort={sort}
                  order={order}
                  onSort={onSort}
                />
                <th>Status</th>
                <th>Tunnel</th>
                <th>Usage</th>
                <SortHeader
                  label="Last sign-in"
                  col="last_sign_in_at"
                  sort={sort}
                  order={order}
                  onSort={onSort}
                />
                <SortHeader
                  label="Created"
                  col="created_at"
                  sort={sort}
                  order={order}
                  onSort={onSort}
                />
              </tr>
            </thead>
            <tbody>
              {users.map((u) => (
                <tr
                  key={u.id}
                  className={selected.has(u.id) ? 'row-selected' : undefined}
                >
                  <td className="col-check">
                    <input
                      type="checkbox"
                      aria-label={`Select ${u.email ?? u.id}`}
                      checked={selected.has(u.id)}
                      onChange={() => toggleRow(u.id)}
                    />
                  </td>
                  <td>
                    <Link href={`/admin/users/${u.id}`}>
                      {u.email ?? u.id}
                    </Link>
                  </td>
                  <td>
                    {u.role === 'admin' ? (
                      <span className="badge badge-admin">admin</span>
                    ) : (
                      <span className="badge">user</span>
                    )}
                  </td>
                  <td>
                    {isBanned(u) ? (
                      <span className="badge badge-banned">banned</span>
                    ) : u.email_confirmed_at ? (
                      <span className="badge badge-ok">confirmed</span>
                    ) : (
                      <span className="badge">unconfirmed</span>
                    )}
                  </td>
                  <td>{u.tunnel ? u.tunnel.subdomain : '—'}</td>
                  <td>
                    {u.tunnel
                      ? `${formatBytes(u.tunnel.bytes_used)} / ${formatBytes(
                          u.tunnel.quota_bytes,
                        )}`
                      : '—'}
                  </td>
                  <td>{formatDate(u.last_sign_in_at)}</td>
                  <td>{formatDate(u.created_at)}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      <div className="row" style={{ marginTop: '1rem' }}>
        <button
          className="secondary btn-sm"
          type="button"
          disabled={page <= 1}
          onClick={() => setPage((p) => Math.max(1, p - 1))}
        >
          Prev
        </button>
        <span className="muted">
          Page {page} of {totalPages} ({total} total)
        </span>
        <button
          className="secondary btn-sm"
          type="button"
          disabled={page >= totalPages}
          onClick={() => setPage((p) => p + 1)}
        >
          Next
        </button>
      </div>
    </div>
  );
}
