import { useState } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import { Copy, Ban, Plus, KeyRound, Loader2 } from 'lucide-react';
import { useClient } from 'hooks/useClient';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button';
import { copyToClipboard } from 'helpers/clipboard';

// Your own API keys: one per app or device, so a phone holds a credential you can revoke on its own
// instead of a session everything shares.
//
// The key is returned by POST and never again — the column holds a SHA-256, so there is nothing to read
// back. That drives the whole screen: the new key sits in a panel that stays put until dismissed, because
// the moment it disappears the only remedy is to revoke and mint another. Same reasoning as the DAV app
// passwords beside this.
//
// A key carries your full account authority. It is not more than you already had — it is what your
// password could already do — but it does mean a leaked key is a leaked account, so revoke is one click
// and "last used" is on every row: a key that has never been used is the tell that something was set up
// wrong, and one used from somewhere you did not expect is the tell that matters more.

type ApiKey = {
  id: number;
  name: string;
  prefix: string;
  lastUsedAt: string | null;
  expiresAt: string | null;
  revokedAt: string | null;
  createdAt: string;
};

type Minted = { key: string; name: string };

const API_KEYS_QUERY_KEY = ['API_KEYS'];

const formatDate = (value: string | null) =>
  value ? new Date(value).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }) : null;

const copy = async (text: string) => {
  try {
    await copyToClipboard(text);
    toast.success('Key copied');
  } catch {
    toast.error('Could not copy — select and copy manually');
  }
};

export const ApiKeys = () => {
  const client = useClient();
  const queryClient = useQueryClient();
  const [name, setName] = useState('');
  const [isCreating, setIsCreating] = useState(false);
  const [minted, setMinted] = useState<Minted | null>(null);

  const { data, isLoading } = useQuery<{ keys: ApiKey[] }>({
    queryKey: API_KEYS_QUERY_KEY,
    queryFn: () => client.get<{ keys: ApiKey[] }>('/api-keys'),
  });

  const refresh = () => queryClient.invalidateQueries({ queryKey: API_KEYS_QUERY_KEY });

  const create = async () => {
    const trimmed = name.trim();
    if (!trimmed || isCreating) return;
    setIsCreating(true);
    try {
      const res = await client.post<{ key: string }>('/api-keys', { name: trimmed });
      setMinted({ key: res.key, name: trimmed });
      setName('');
      await refresh();
    } catch (ex) {
      toast.error(ex instanceof Error ? ex.message : 'Could not create the key');
    } finally {
      setIsCreating(false);
    }
  };

  const revoke = async (id: number) => {
    try {
      await client.delete(`/api-keys/${id}`);
      toast.success('Revoked — anything using that key is signed out');
      await refresh();
    } catch {
      toast.error('Could not revoke');
    }
  };

  const keys = data?.keys ?? [];

  if (isLoading) {
    return (
      <div className="flex items-center gap-2 p-6 text-sm text-muted-foreground">
        <Loader2 className="h-4 w-4 animate-spin" /> Loading keys…
      </div>
    );
  }

  return (
    <div className="grid gap-5">
      <p className="text-sm text-duck-dark/70 dark:text-foreground/70">
        An API key signs in as you, without a password and without expiring. Give each app or device its own, so you can
        cut one off without touching the rest. Send it as{' '}
        <code className="font-mono text-xs">Authorization: Bearer …</code>.
      </p>

      {minted && (
        <div className="grid gap-3 rounded-lg border-2 border-duck-teal/40 bg-duck-teal/5 p-4">
          <div className="text-sm font-medium text-duck-dark dark:text-foreground">
            Key for “{minted.name}” — shown once
          </div>
          <p className="text-xs text-duck-dark/60 dark:text-foreground/60">
            This is the only time it is displayed. It's stored hashed, so it can't be shown again — if you lose it,
            revoke this entry and make another.
          </p>

          <div className="flex items-center gap-2">
            <code className="min-w-0 flex-1 truncate rounded bg-background/70 px-2 py-1.5 font-mono text-xs">
              {minted.key}
            </code>
            <Button variant="ghost" size="sm" onClick={() => copy(minted.key)} title="Copy key">
              <Copy className="h-3.5 w-3.5" />
            </Button>
          </div>

          <Button variant="outline" size="sm" className="justify-self-start" onClick={() => setMinted(null)}>
            I've saved it
          </Button>
        </div>
      )}

      <div className="flex items-end gap-2">
        <Label className="grid flex-1 gap-2">
          <span className="text-duck-dark/70 dark:text-foreground/70">New key</span>
          <Input
            className="h-11 bg-background/60 border-duck-dark/20"
            value={name}
            onChange={(ev) => setName(ev.target.value)}
            onKeyDown={(ev) => ev.key === 'Enter' && create()}
            placeholder="iPhone, Music app, laptop CLI…"
          />
        </Label>
        <Button onClick={create} disabled={!name.trim() || isCreating} className="h-11">
          <Plus className="mr-1.5 h-4 w-4" />
          Create
        </Button>
      </div>

      {keys.length === 0 ? (
        <div className="rounded-lg border border-dashed border-duck-dark/15 dark:border-foreground/15 p-6 text-center text-sm text-duck-dark/40 dark:text-foreground/40">
          No keys yet.
        </div>
      ) : (
        <div className="grid gap-2">
          {keys.map((entry) => {
            const expired = !!entry.expiresAt && new Date(entry.expiresAt).getTime() <= Date.now();
            const dead = !!entry.revokedAt || expired;
            const lastUsed = formatDate(entry.lastUsedAt);
            return (
              <div
                key={entry.id}
                className={`flex items-center gap-3 rounded-lg border p-3 ${
                  dead
                    ? 'border-duck-dark/10 dark:border-foreground/10 opacity-50'
                    : 'border-duck-dark/15 dark:border-foreground/15'
                }`}
              >
                <KeyRound className="h-4 w-4 shrink-0 text-duck-teal/70" />
                <div className="min-w-0 flex-1">
                  <div className="truncate text-sm text-duck-dark dark:text-foreground">
                    {entry.name}
                    {entry.revokedAt && <span className="ml-2 text-xs text-red-500">revoked</span>}
                    {!entry.revokedAt && expired && <span className="ml-2 text-xs text-red-500">expired</span>}
                  </div>
                  <div className="text-xs text-duck-dark/40 dark:text-foreground/40">
                    <code className="font-mono">{entry.prefix}…</code>
                    {' · '}
                    {/* "never used" is the tell that an app was configured wrong, so it earns its own wording. */}
                    {lastUsed ? `last used ${lastUsed}` : 'never used'}
                  </div>
                </div>
                {!dead && (
                  <Button
                    variant="ghost"
                    size="sm"
                    onClick={() => revoke(entry.id)}
                    title="Revoke"
                    className="hover:text-red-500"
                  >
                    <Ban className="h-3.5 w-3.5" />
                  </Button>
                )}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
};
