import { useEffect, useRef, useState } from 'preact/hooks';
import type { Bar, Drink } from '@wutzcalc/shared';
import { formatCents } from '../api';

async function j<T = any>(res: Response): Promise<T> {
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  return res.json();
}

// Best-effort error message from a failed response. Prefers RFC 7807
// (application/problem+json) `detail`/`title` — what the admin routes send
// — falls back to the older ad hoc `{ error }` shape (still used by a few
// not-yet-converted endpoints), then plain status text.
async function errText(res: Response): Promise<string> {
  try {
    const body = await res.json();
    return body.detail ?? body.title ?? body.error ?? res.statusText;
  } catch {
    return res.statusText;
  }
}

// `Drink`/`Bar` come from @wutzcalc/shared — the tablet code already did
// this, but Admin.tsx used to re-declare its own near-identical copies,
// which had already drifted (its local Drink.archived was typed `number`
// while the shared one was `boolean`, even though both describe the exact
// same wire field). BarRow/stats shapes below are admin-only — not part
// of the wire contract the tablet also consumes — so they stay local.
interface BarRow extends Bar { drink_ids: number[] }

export function Admin() {
  const [authed, setAuthed] = useState<boolean | null>(null);

  useEffect(() => {
    fetch('/admin/api/me').then(j).then(r => setAuthed(r.authed)).catch(() => setAuthed(false));
  }, []);

  if (authed === null) return <div class="admin"><p>Lade…</p></div>;
  if (!authed) return <Login onAuthed={() => setAuthed(true)} />;
  return <Dashboard onLogout={() => setAuthed(false)} />;
}

function Login({ onAuthed }: { onAuthed: () => void }) {
  const [pw, setPw] = useState('');
  const [err, setErr] = useState<string | null>(null);

  async function submit(e: Event) {
    e.preventDefault();
    setErr(null);
    try {
      await fetch('/admin/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ password: pw }),
      }).then(j);
      onAuthed();
    } catch (e: any) {
      setErr(String(e));
    }
  }

  return (
    <form class="admin" onSubmit={submit}>
      <div class="login">
        <h1>Backoffice</h1>
        <input
          type="password"
          placeholder="Passwort"
          value={pw}
          onInput={(e: any) => setPw(e.currentTarget.value)}
        />
        {err && <div style="color:var(--danger)">{err}</div>}
        <button type="submit">Anmelden</button>
      </div>
    </form>
  );
}

function Dashboard({ onLogout }: { onLogout: () => void }) {
  // Drinks lives here, not inside <Drinks>, so <Bars> (specifically
  // BarDrinkEditor's "available to add" list) sees a newly added/edited
  // drink immediately. Each component used to fetch its own independent
  // copy of the drinks list, so adding a drink updated only <Drinks>'s
  // copy — <Bars> kept rendering its stale pre-add snapshot until a full
  // page reload re-mounted everything.
  const [drinks, setDrinks] = useState<Drink[]>([]);
  function reloadDrinks() {
    fetch('/admin/api/drinks').then(j).then(setDrinks).catch(e => alert(`Fehler beim Laden: ${e}`));
  }
  useEffect(reloadDrinks, []);

  return (
    <div class="admin">
      <div class="row" style="justify-content: space-between">
        <h1 style="margin:0">wutzcalc Backoffice</h1>
        <button onClick={async () => { await fetch('/admin/logout', { method: 'POST' }); onLogout(); }}>
          Abmelden
        </button>
      </div>
      <div class="row" style="justify-content: space-between; align-items: center">
        <h2 style="margin:0">Statistik</h2>
        <a href="/stats">Statistik ansehen →</a>
      </div>
      <Exports />
      <Drinks drinks={drinks} reload={reloadDrinks} />
      <Bars drinks={drinks} />
    </div>
  );
}


function Exports() {
  return (
    <>
      <h2>CSV-Export</h2>
      <div class="row">
        <a href="/admin/api/export.csv?what=transactions"><button>Transaktionen</button></a>
        <a href="/admin/api/export.csv?what=items"><button>Positionen</button></a>
      </div>
    </>
  );
}

function Drinks({ drinks: list, reload }: { drinks: Drink[]; reload: () => void }) {
  const [name, setName] = useState('');
  const [price, setPrice] = useState('');
  const [adding, setAdding] = useState(false);

  async function add() {
    const cents = Math.round(parseFloat(price) * 100);
    if (!name || !Number.isFinite(cents) || adding) return;
    setAdding(true);
    try {
      const res = await fetch('/admin/api/drinks', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name, price_cents: cents }),
      });
      if (!res.ok) { alert(`Fehler: ${await errText(res)}`); return; }
      setName(''); setPrice(''); reload();
    } finally {
      setAdding(false);
    }
  }

  async function patch(id: number, body: Partial<Drink>) {
    const res = await fetch(`/admin/api/drinks/${id}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    });
    if (!res.ok) { alert(`Fehler: ${await errText(res)}`); return; }
    reload();
  }

  async function del(d: Drink) {
    if (!confirm(`Getränk „${d.name}“ wirklich löschen?`)) return;
    const res = await fetch(`/admin/api/drinks/${d.id}`, { method: 'DELETE' });
    if (!res.ok) { alert(`Fehler: ${await errText(res)}`); return; }
    reload();
  }

  return (
    <>
      <h2>Getränke</h2>
      <table>
        <thead><tr><th>Name</th><th>Preis</th><th>Status</th><th></th></tr></thead>
        <tbody>
          {list.map(d => (
            <tr key={d.id}>
              <td>
                <input value={d.name} onChange={(e: any) => patch(d.id, { name: e.currentTarget.value })} />
              </td>
              <td>
                <input
                  type="number"
                  step="0.01"
                  value={(d.price_cents / 100).toFixed(2)}
                  onChange={(e: any) => patch(d.id, { price_cents: Math.round(parseFloat(e.currentTarget.value) * 100) })}
                />
              </td>
              <td class={d.archived ? 'muted' : ''}>{d.archived ? 'archiviert' : 'aktiv'}</td>
              <td>
                <div class="row" style="margin:0">
                  <button onClick={() => patch(d.id, { archived: !d.archived } as any)}>
                    {d.archived ? 'aktivieren' : 'archivieren'}
                  </button>
                  <button class="danger" onClick={() => del(d)}>löschen</button>
                </div>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
      <div class="row">
        <input placeholder="Name" value={name} onInput={(e: any) => setName(e.currentTarget.value)} />
        <input placeholder="Preis €" type="number" step="0.01" value={price} onInput={(e: any) => setPrice(e.currentTarget.value)} />
        <button onClick={add} disabled={adding}>Hinzufügen</button>
      </div>
    </>
  );
}

function BarDrinkEditor({
  bar,
  allDrinks,
  onChange,
}: {
  bar: BarRow;
  allDrinks: Drink[];
  onChange: (ids: number[]) => void;
}) {
  const byId = new Map(allDrinks.map(d => [d.id, d]));

  // `onChange` fires a fire-and-forget PATCH (see Bars.patch) that only
  // resolves into a fresh `bar` prop once its `reload()` completes. Deriving
  // `selected` straight from `bar.drink_ids` meant a second click before that
  // round-trip landed re-derived its reorder/add/remove from the same stale
  // array the first click started from — whichever PATCH the server applied
  // last won, silently discarding the rest. `pendingIds` tracks our own
  // in-flight optimistic state so back-to-back clicks chain off each other
  // instead of the lagging prop; it's cleared whenever the server sends back
  // a fresh `bar.drink_ids` (our own round-trip landing, or an edit from
  // elsewhere), deferring to that as the new source of truth.
  const [pendingIds, setPendingIds] = useState<number[] | null>(null);
  useEffect(() => {
    setPendingIds(null);
  }, [bar.drink_ids]);

  const selected = (pendingIds ?? bar.drink_ids).filter(id => {
    const d = byId.get(id);
    return d && !d.archived;
  });
  const available = allDrinks.filter(d => !d.archived && !selected.includes(d.id));

  const dragFrom = useRef<number | null>(null);
  const [dragOver, setDragOver] = useState<number | null>(null);

  function commit(next: number[]) {
    setPendingIds(next);
    onChange(next);
  }
  function remove(idx: number) {
    commit(selected.filter((_, i) => i !== idx));
  }
  function add(id: number) {
    commit([...selected, id]);
  }
  function reorder(from: number, to: number) {
    if (from === to || from < 0 || to < 0 || from >= selected.length || to >= selected.length) return;
    const next = selected.slice();
    const [moved] = next.splice(from, 1);
    next.splice(to, 0, moved!);
    commit(next);
  }

  return (
    <>
      <div style="margin-top:8px; font-size:14px; opacity:0.7">Aktive Getränke (zum Sortieren ziehen)</div>
      <ul class="dnd-list">
        {selected.length === 0 && <li class="muted">Keine</li>}
        {selected.map((id, idx) => {
          const d = byId.get(id);
          if (!d) return null;
          return (
            <li
              key={id}
              class={`dnd-item ${dragOver === idx ? 'drop-target' : ''}`}
              draggable
              onDragStart={(e: any) => {
                dragFrom.current = idx;
                e.dataTransfer.effectAllowed = 'move';
              }}
              onDragOver={(e: any) => {
                e.preventDefault();
                e.dataTransfer.dropEffect = 'move';
                if (dragOver !== idx) setDragOver(idx);
              }}
              onDragLeave={() => { if (dragOver === idx) setDragOver(null); }}
              onDrop={(e: any) => {
                e.preventDefault();
                const from = dragFrom.current;
                dragFrom.current = null;
                setDragOver(null);
                if (from != null) reorder(from, idx);
              }}
              onDragEnd={() => { dragFrom.current = null; setDragOver(null); }}
            >
              <span class="grip" aria-hidden="true">⋮⋮</span>
              <span class="dnd-name">{idx + 1}. {d.name}</span>
              {/* Drag-and-drop above doesn't fire on touch-only input (no
                  mouse) and has no keyboard equivalent — these buttons are
                  the fallback that works regardless of input method. */}
              <button onClick={() => reorder(idx, idx - 1)} disabled={idx === 0} aria-label="nach oben">
                ▲
              </button>
              <button
                onClick={() => reorder(idx, idx + 1)}
                disabled={idx === selected.length - 1}
                aria-label="nach unten"
              >
                ▼
              </button>
              <button onClick={() => remove(idx)}>×</button>
            </li>
          );
        })}
      </ul>
      {available.length > 0 && (
        <>
          <div style="font-size:14px; opacity:0.7">Hinzufügen</div>
          <div class="row">
            {available.map(d => (
              <button key={d.id} onClick={() => add(d.id)}>+ {d.name}</button>
            ))}
          </div>
        </>
      )}
    </>
  );
}

function Bars({ drinks }: { drinks: Drink[] }) {
  const [bars, setBars] = useState<BarRow[]>([]);
  const [newName, setNewName] = useState('');
  const [addingBar, setAddingBar] = useState(false);

  function reload() {
    fetch('/admin/api/bars').then(j).then(setBars).catch(e => alert(`Fehler beim Laden: ${e}`));
  }
  useEffect(reload, []);

  async function patch(id: number, body: Partial<BarRow>) {
    const res = await fetch(`/admin/api/bars/${id}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    });
    if (!res.ok) alert(`Fehler: ${await errText(res)}`);
    reload();
  }

  async function delBar(b: BarRow) {
    if (!confirm(`Tresen „${b.name}“ wirklich löschen?`)) return;
    const res = await fetch(`/admin/api/bars/${b.id}`, { method: 'DELETE' });
    if (!res.ok) { alert(`Fehler: ${await errText(res)}`); return; }
    reload();
  }

  async function addBar() {
    const name = newName.trim();
    if (!name || addingBar) return;
    setAddingBar(true);
    try {
      const res = await fetch('/admin/api/bars', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name, pfand_cents: 200 }),
      });
      if (!res.ok) {
        alert(`Fehler: ${await errText(res)}`);
        return;
      }
      setNewName('');
      reload();
    } finally {
      setAddingBar(false);
    }
  }

  return (
    <>
      <h2>Bars</h2>
      {bars.map(b => (
        <div key={b.id} style="margin-bottom:16px; padding:8px; border:1px solid var(--border); border-radius:6px">
          <div class="row">
            <label>
              Name
              <input
                value={b.name}
                onChange={(e: any) => patch(b.id, { name: e.currentTarget.value } as any)}
              />
            </label>
            <label>
              Pfand €
              <input
                type="number"
                step="0.01"
                value={(b.pfand_cents / 100).toFixed(2)}
                onChange={(e: any) =>
                  patch(b.id, { pfand_cents: Math.round(parseFloat(e.currentTarget.value) * 100) } as any)
                }
              />
            </label>
            <button class="danger" onClick={() => delBar(b)} style="margin-left:auto">Tresen löschen</button>
          </div>
          <BarDrinkEditor
            bar={b}
            allDrinks={drinks}
            onChange={(ids) => patch(b.id, { drink_ids: ids } as any)}
          />
        </div>
      ))}
      <div class="row">
        <input
          placeholder="Neue Bar"
          value={newName}
          onInput={(e: any) => setNewName(e.currentTarget.value)}
        />
        <button onClick={addBar} disabled={addingBar}>Bar hinzufügen</button>
      </div>
    </>
  );
}
