<script lang="ts">
  import { pageTitle } from '$lib/branding';
  import { useQueryClient } from '@tanstack/svelte-query';
  import { qk } from '$lib/api/queries';
  import {
    updateUserAdmin,
    createInvite,
    deleteInvite,
    createUser,
    deleteUser,
    resetUserPassword,
    updateUserAutoApprove,
    createAdminUsersQuery,
    createAdminInvitesQuery,
    type AdminUser,
    type AdminInvite,
    type CreateUserInput
  } from '$lib/api/admin';
  import { errCode } from '$lib/api/errors';
  import { pushToast } from '$lib/stores/toast.svelte';
  import Modal from '$lib/components/Modal.svelte';
  import RowActionsMenu, { type RowAction } from '$lib/components/RowActionsMenu.svelte';
  import { Shield, ShieldOff, CheckCircle2, XCircle, KeyRound, Trash2 } from 'lucide-svelte';

  const client = useQueryClient();

  const usersStore = createAdminUsersQuery();
  const usersQuery = $derived($usersStore);
  const users = $derived((usersQuery.data ?? []) as AdminUser[]);

  const invitesStore = createAdminInvitesQuery();
  const invitesQuery = $derived($invitesStore);
  const invites = $derived((invitesQuery.data ?? []) as AdminInvite[]);

  let saving = $state(false);

  // Modal state
  let showCreateModal = $state(false);
  let createForm = $state<CreateUserInput & { confirmPassword: string }>({
    username: '',
    password: '',
    confirmPassword: '',
    display_name: '',
    is_admin: false,
  });

  let resetPasswordTarget = $state<AdminUser | null>(null);
  let resetPasswordValue = $state('');
  let resetPasswordConfirm = $state('');

  let confirmDeleteTarget = $state<AdminUser | null>(null);

  async function onToggleAdmin(u: AdminUser) {
    saving = true;
    try {
      await updateUserAdmin(u.id, !u.is_admin);
      await client.invalidateQueries({ queryKey: qk.adminUsers() });
      pushToast(u.is_admin ? `${u.username} is no longer an admin.` : `${u.username} is now an admin.`);
    } catch (e: unknown) {
      const code = errCode(e);
      if (code === 'last_admin') {
        pushToast(`Can't remove the last admin — promote someone else first.`, 'error');
      } else {
        pushToast(`Action failed: ${code}`, 'error');
      }
    } finally {
      saving = false;
    }
  }

  async function onSubmitCreate(e: SubmitEvent) {
    e.preventDefault();
    if (createForm.password !== createForm.confirmPassword) {
      pushToast('Passwords do not match.', 'error');
      return;
    }
    saving = true;
    try {
      await createUser({
        username: createForm.username,
        password: createForm.password,
        display_name: createForm.display_name || undefined,
        is_admin: createForm.is_admin,
      });
      await client.invalidateQueries({ queryKey: qk.adminUsers() });
      showCreateModal = false;
      createForm = { username: '', password: '', confirmPassword: '', display_name: '', is_admin: false };
      pushToast('User created.');
    } catch (e: unknown) {
      pushToast(createUserErrorMessage(errCode(e)), 'error');
    } finally {
      saving = false;
    }
  }

  function createUserErrorMessage(code: string | undefined): string {
    switch (code) {
      case 'username_taken': return 'That username is already taken.';
      case 'username_invalid': return 'Username must be 3–32 characters: letters, numbers, underscores, hyphens.';
      case 'password_too_short': return 'Password must be at least 8 characters.';
      default: return `Create failed: ${code ?? 'unknown'}`;
    }
  }

  async function onConfirmDelete() {
    if (!confirmDeleteTarget) return;
    saving = true;
    try {
      await deleteUser(confirmDeleteTarget.id);
      await client.invalidateQueries({ queryKey: qk.adminUsers() });
      pushToast(`Deleted ${confirmDeleteTarget.username}.`);
      confirmDeleteTarget = null;
    } catch (e: unknown) {
      const code = errCode(e);
      if (code === 'last_admin') {
        pushToast(`Can't delete the last admin — promote someone else first.`, 'error');
      } else {
        pushToast(`Delete failed: ${code}`, 'error');
      }
    } finally {
      saving = false;
    }
  }

  async function onSubmitResetPassword(e: SubmitEvent) {
    e.preventDefault();
    if (!resetPasswordTarget) return;
    if (resetPasswordValue !== resetPasswordConfirm) {
      pushToast('Passwords do not match.', 'error');
      return;
    }
    saving = true;
    try {
      await resetUserPassword(resetPasswordTarget.id, resetPasswordValue);
      pushToast(`Password reset for ${resetPasswordTarget.username}.`);
      resetPasswordTarget = null;
      resetPasswordValue = '';
      resetPasswordConfirm = '';
    } catch (e: unknown) {
      const code = errCode(e);
      if (code === 'password_too_short') {
        pushToast('Password must be at least 8 characters.', 'error');
      } else {
        pushToast(`Reset failed: ${code}`, 'error');
      }
    } finally {
      saving = false;
    }
  }

  async function onToggleAutoApprove(u: AdminUser) {
    saving = true;
    try {
      await updateUserAutoApprove(u.id, !u.auto_approve_requests);
      await client.invalidateQueries({ queryKey: qk.adminUsers() });
      pushToast(u.auto_approve_requests
        ? `Auto-approve disabled for ${u.username}.`
        : `Auto-approve enabled for ${u.username}.`);
    } catch (e: unknown) {
      pushToast(`Toggle failed: ${errCode(e)}`, 'error');
    } finally {
      saving = false;
    }
  }

  async function onGenerateInvite() {
    saving = true;
    try {
      await createInvite();
      await client.invalidateQueries({ queryKey: qk.adminInvites() });
      pushToast('Invite generated. Copy the token from the list below.');
    } catch (e: unknown) {
      pushToast(`Generate failed: ${errCode(e)}`, 'error');
    } finally {
      saving = false;
    }
  }

  async function onRevokeInvite(invite: AdminInvite) {
    saving = true;
    try {
      await deleteInvite(invite.token);
      await client.invalidateQueries({ queryKey: qk.adminInvites() });
      pushToast('Invite revoked.');
    } catch (e: unknown) {
      pushToast(`Revoke failed: ${errCode(e)}`, 'error');
    } finally {
      saving = false;
    }
  }

  async function copyToken(token: string) {
    try {
      await navigator.clipboard.writeText(token);
      pushToast('Token copied to clipboard.');
    } catch {
      pushToast('Copy failed — select the token and copy manually.', 'error');
    }
  }

  function formatDate(iso: string): string {
    return new Date(iso).toLocaleString();
  }
</script>

<svelte:head><title>{pageTitle('Admin · Users')}</title></svelte:head>

<div class="space-y-8">
  <header class="space-y-1">
    <h2 class="font-display text-2xl font-medium text-text-primary">Users</h2>
    <p class="text-text-secondary">
      Manage user accounts and invites for this Minstrel instance.
    </p>
  </header>

  <!-- Accounts section -->
  <section class="space-y-3">
    <div class="flex items-baseline justify-between">
      <h3 class="font-display text-xl font-medium text-text-primary">Accounts</h3>
      <button
        type="button"
        disabled={saving}
        class="inline-flex items-center gap-1 rounded-md bg-action-primary px-3 py-1.5 text-sm text-action-fg disabled:cursor-not-allowed disabled:opacity-50"
        onclick={() => { showCreateModal = true; }}
      >
        New user
      </button>
    </div>
    {#if usersQuery.isPending}
      <p class="text-text-secondary">Reading users…</p>
    {:else if usersQuery.isError}
      <p class="text-error">Couldn't load users.</p>
    {:else if users.length === 0}
      <p class="text-text-secondary">No users yet.</p>
    {:else}
      <ul class="divide-y divide-border rounded-lg border border-border bg-surface">
        {#each users as u (u.id)}
          {@const primary: RowAction = {
            icon: u.is_admin ? ShieldOff : Shield,
            label: u.is_admin ? 'Remove admin' : 'Make admin',
            onclick: () => onToggleAdmin(u),
            disabled: saving
          }}
          {@const secondary: RowAction[] = [
            {
              icon: u.auto_approve_requests ? XCircle : CheckCircle2,
              label: u.auto_approve_requests ? 'Disable auto-approve' : 'Enable auto-approve',
              onclick: () => onToggleAutoApprove(u),
              disabled: saving
            },
            {
              icon: KeyRound,
              label: 'Reset password',
              onclick: () => { resetPasswordTarget = u; },
              disabled: saving
            },
            {
              icon: Trash2,
              label: 'Delete',
              onclick: () => { confirmDeleteTarget = u; },
              disabled: saving,
              danger: true
            }
          ]}
          <li class="flex flex-wrap items-center gap-2 p-3" data-testid="user-row" data-user-id={u.id}>
            <div class="min-w-0 flex-1 space-y-0.5">
              <div class="flex flex-wrap items-center gap-2">
                <span class="text-base font-medium text-text-primary">{u.username}</span>
                {#if u.is_admin}
                  <span
                    class="inline-flex items-center rounded-full bg-accent-tint px-2 py-0.5 text-xs text-accent"
                    data-testid="admin-badge"
                  >admin</span>
                {/if}
                {#if u.auto_approve_requests}
                  <span
                    class="inline-flex items-center rounded-full border border-border px-2 py-0.5 text-xs text-text-secondary"
                    data-testid="auto-approve-badge"
                  >auto-approve</span>
                {/if}
              </div>
              <div class="text-sm text-text-secondary">
                {#if u.display_name}{u.display_name} · {/if}created {formatDate(u.created_at)}
              </div>
            </div>
            <div class="shrink-0">
              <RowActionsMenu {primary} {secondary} />
            </div>
          </li>
        {/each}
      </ul>
    {/if}
  </section>

  <!-- Invites section -->
  <section class="space-y-3">
    <div class="flex items-baseline justify-between">
      <h3 class="font-display text-xl font-medium text-text-primary">Invites</h3>
      <button
        type="button"
        disabled={saving}
        class="inline-flex items-center gap-1 rounded-md bg-action-primary px-3 py-1.5 text-sm text-action-fg disabled:cursor-not-allowed disabled:opacity-50"
        onclick={onGenerateInvite}
      >
        Generate invite
      </button>
    </div>
    <p class="text-sm text-text-secondary">
      Invites expire after 24 hours and can be used once. Share the token with the
      person you want to invite — they enter it on the registration page.
    </p>
    {#if invitesQuery.isPending}
      <p class="text-text-secondary">Reading invites…</p>
    {:else if invitesQuery.isError}
      <p class="text-error">Couldn't load invites.</p>
    {:else if invites.length === 0}
      <p class="text-sm text-text-secondary">No active invites.</p>
    {:else}
      <ul class="divide-y divide-border rounded-lg border border-border bg-surface">
        {#each invites as inv (inv.token)}
          <li class="flex items-center gap-3 p-3" data-testid="invite-row">
            <code class="min-w-0 flex-1 truncate font-mono text-xs text-text-secondary">{inv.token}</code>
            <span class="shrink-0 text-xs text-text-secondary">
              expires {formatDate(inv.expires_at)}
            </span>
            <button
              type="button"
              aria-label={`Copy token for invite ${inv.token}`}
              class="inline-flex items-center gap-1 rounded-md border border-border bg-transparent px-3 py-1.5 text-sm text-text-secondary hover:bg-surface-hover hover:text-text-primary"
              onclick={() => copyToken(inv.token)}
            >
              Copy
            </button>
            <button
              type="button"
              disabled={saving}
              aria-label={`Revoke invite ${inv.token}`}
              class="inline-flex items-center gap-1 rounded-md border border-border bg-transparent px-3 py-1.5 text-sm text-text-secondary hover:bg-surface-hover hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-50"
              onclick={() => onRevokeInvite(inv)}
            >
              Revoke
            </button>
          </li>
        {/each}
      </ul>
    {/if}
  </section>
</div>

<Modal
  title="New user"
  open={showCreateModal}
  onClose={() => { showCreateModal = false; }}
>
  <form onsubmit={onSubmitCreate} class="space-y-3">
    <div>
      <label for="cu-username" class="block text-sm text-text-secondary">Username</label>
      <input
        id="cu-username"
        type="text"
        required
        minlength="3"
        maxlength="32"
        bind:value={createForm.username}
        class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent"
      />
    </div>
    <div>
      <label for="cu-display" class="block text-sm text-text-secondary">
        Display name <span class="text-text-muted">(optional)</span>
      </label>
      <input
        id="cu-display"
        type="text"
        maxlength="64"
        bind:value={createForm.display_name}
        class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent"
      />
    </div>
    <div>
      <label for="cu-password" class="block text-sm text-text-secondary">Password</label>
      <input
        id="cu-password"
        type="password"
        required
        minlength="8"
        bind:value={createForm.password}
        class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent"
      />
    </div>
    <div>
      <label for="cu-confirm" class="block text-sm text-text-secondary">Confirm password</label>
      <input
        id="cu-confirm"
        type="password"
        required
        minlength="8"
        bind:value={createForm.confirmPassword}
        class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent"
      />
    </div>
    <label class="flex items-center gap-2 text-sm text-text-secondary">
      <input type="checkbox" bind:checked={createForm.is_admin} />
      Make this user an admin
    </label>
    <div class="flex justify-end gap-2 pt-2">
      <button
        type="button"
        disabled={saving}
        class="rounded-md border border-border px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary"
        onclick={() => { showCreateModal = false; }}
      >
        Cancel
      </button>
      <button
        type="submit"
        disabled={saving}
        class="inline-flex items-center gap-1 rounded-md bg-action-primary px-3 py-1.5 text-sm text-action-fg disabled:cursor-not-allowed disabled:opacity-50"
      >
        {saving ? 'Creating…' : 'Create user'}
      </button>
    </div>
  </form>
</Modal>

<Modal
  title={resetPasswordTarget ? `Reset password for ${resetPasswordTarget.username}` : ''}
  open={resetPasswordTarget !== null}
  onClose={() => { resetPasswordTarget = null; resetPasswordValue = ''; resetPasswordConfirm = ''; }}
>
  <p class="-mt-2 mb-3 text-sm text-text-secondary">
    The user will need to log in with the new password.
  </p>
  <form onsubmit={onSubmitResetPassword} class="space-y-3">
    <div>
      <label for="rp-password" class="block text-sm text-text-secondary">New password</label>
      <input
        id="rp-password"
        type="password"
        required
        minlength="8"
        bind:value={resetPasswordValue}
        class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent"
      />
    </div>
    <div>
      <label for="rp-confirm" class="block text-sm text-text-secondary">Confirm</label>
      <input
        id="rp-confirm"
        type="password"
        required
        minlength="8"
        bind:value={resetPasswordConfirm}
        class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent"
      />
    </div>
    <div class="flex justify-end gap-2 pt-2">
      <button
        type="button"
        disabled={saving}
        class="rounded-md border border-border px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary"
        onclick={() => { resetPasswordTarget = null; resetPasswordValue = ''; resetPasswordConfirm = ''; }}
      >
        Cancel
      </button>
      <button
        type="submit"
        disabled={saving}
        class="inline-flex items-center gap-1 rounded-md bg-action-primary px-3 py-1.5 text-sm text-action-fg disabled:cursor-not-allowed disabled:opacity-50"
      >
        {saving ? 'Resetting…' : 'Set password'}
      </button>
    </div>
  </form>
</Modal>

<Modal
  title={confirmDeleteTarget ? `Delete ${confirmDeleteTarget.username}?` : ''}
  open={confirmDeleteTarget !== null}
  onClose={() => { confirmDeleteTarget = null; }}
>
  <p class="-mt-2 text-sm text-text-secondary">
    This permanently deletes the account and cascades through plays, likes,
    sessions, and quarantine. The action cannot be undone.
  </p>
  <div class="mt-5 flex justify-end gap-2">
    <button
      type="button"
      disabled={saving}
      class="rounded-md border border-border px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary"
      onclick={() => { confirmDeleteTarget = null; }}
    >
      Cancel
    </button>
    <button
      type="button"
      disabled={saving}
      class="inline-flex items-center gap-1 rounded-md bg-action-destructive px-3 py-1.5 text-sm text-action-fg disabled:cursor-not-allowed disabled:opacity-50"
      onclick={onConfirmDelete}
    >
      {saving ? 'Deleting…' : 'Delete'}
    </button>
  </div>
</Modal>
