// Per-user drilldown: profile, role assignment, API keys, usage + quota.
// Opened from the Users tab via the row's "View" action.

import { useCallback, useEffect, useState } from "react"
import {
  CheckCircle2,
  Copy,
  KeyRound,
  Plus,
  RefreshCw,
  Shield,
  ShieldCheck,
  ShieldOff,
  Trash2,
  X,
} from "lucide-react"

import { ArcadiaError, useArcadiaClient } from "@crema/arcadia-client"
import { AlertBanner, ConfirmDialog } from "@crema/feedback-ui"

import { Badge } from "~/components/ui/badge"
import { Button } from "~/components/ui/button"
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "~/components/ui/dialog"
import { Input } from "~/components/ui/input"
import { Label } from "~/components/ui/label"
import {
  Sheet,
  SheetContent,
  SheetHeader,
  SheetTitle,
} from "~/components/ui/sheet"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"
import {
  createUserApiKey,
  listUserApiKeys,
  revokeUserApiKey,
  type ApiKey,
  type ApiKeyCreated,
} from "~/lib/arcadia/api-keys"
import {
  getUserQuota,
  getUserUsage,
  type UserQuota,
  type UserUsage,
} from "~/lib/arcadia/user-stats"
import {
  assignRole,
  removeRole,
  type User,
} from "~/lib/arcadia/users"
import type { Role } from "~/lib/arcadia/roles"

interface Props {
  user: User | null
  roles: Role[]
  onClose: () => void
  /** Called whenever something changes that the parent may want to re-fetch. */
  onChanged: () => Promise<void>
}

export function UserDetailSheet({ user, roles, onClose, onChanged }: Props) {
  const open = user !== null
  return (
    <Sheet open={open} onOpenChange={(o) => !o && onClose()}>
      <SheetContent side="right" className="w-full sm:max-w-xl flex flex-col gap-0 p-0">
        {user ? (
          <UserDetailBody user={user} roles={roles} onChanged={onChanged} onClose={onClose} />
        ) : null}
      </SheetContent>
    </Sheet>
  )
}

function UserDetailBody({
  user,
  roles,
  onChanged,
  onClose,
}: {
  user: User
  roles: Role[]
  onChanged: () => Promise<void>
  onClose: () => void
}) {
  return (
    <>
      <SheetHeader className="border-b px-6 py-4">
        <SheetTitle className="flex items-center justify-between gap-3">
          <span className="flex flex-col">
            <span>{user.full_name || user.email}</span>
            <span className="text-xs font-normal text-muted-foreground">{user.email}</span>
          </span>
          <Badge variant={user.email_verified ? "default" : "secondary"}>
            {user.email_verified ? "Verified" : "Unverified"}
          </Badge>
        </SheetTitle>
      </SheetHeader>

      <div className="flex-1 overflow-y-auto p-6">
        <Tabs defaultValue="overview">
          <TabsList>
            <TabsTrigger value="overview" data-action={`user-${user.id}-detail-overview`}>
              Overview
            </TabsTrigger>
            <TabsTrigger value="roles" data-action={`user-${user.id}-detail-roles`}>
              Roles
            </TabsTrigger>
            <TabsTrigger value="api-keys" data-action={`user-${user.id}-detail-keys`}>
              API keys
            </TabsTrigger>
          </TabsList>

          <TabsContent value="overview">
            <OverviewPanel user={user} />
          </TabsContent>
          <TabsContent value="roles">
            <RolesPanel user={user} roles={roles} onChanged={onChanged} />
          </TabsContent>
          <TabsContent value="api-keys">
            <ApiKeysPanel user={user} onChanged={onChanged} />
          </TabsContent>
        </Tabs>
      </div>

      <div className="border-t px-6 py-3 flex justify-end">
        <Button variant="outline" onClick={onClose} data-action={`user-${user.id}-detail-close`}>
          Close
        </Button>
      </div>
    </>
  )
}

// --- Overview tab ------------------------------------------------------

function OverviewPanel({ user }: { user: User }) {
  const arcadia = useArcadiaClient()
  const [usage, setUsage] = useState<UserUsage | null>(null)
  const [quota, setQuota] = useState<UserQuota | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)

  useEffect(() => {
    let mounted = true
    setLoading(true)
    setError(null)
    Promise.all([
      getUserUsage(arcadia, user.id).catch((err) => {
        throw err
      }),
      getUserQuota(arcadia, user.id),
    ])
      .then(([u, q]) => {
        if (!mounted) return
        setUsage(u)
        setQuota(q)
      })
      .catch((err) => {
        if (mounted)
          setError(err instanceof ArcadiaError ? err.message : "Failed to load stats.")
      })
      .finally(() => mounted && setLoading(false))
    return () => {
      mounted = false
    }
  }, [arcadia, user.id])

  return (
    <div className="flex flex-col gap-4 pt-4">
      <dl className="grid grid-cols-2 gap-3 text-sm">
        <Stat label="Status" value={user.status} />
        <Stat
          label="Email verified"
          value={user.email_verified ? "Yes" : "No"}
        />
        <Stat
          label="Last sign-in"
          value={user.last_sign_in_at ? new Date(user.last_sign_in_at).toLocaleString() : "Never"}
        />
        <Stat label="Created" value={new Date(user.inserted_at).toLocaleString()} />
        <Stat label="Tenant" value={user.tenant_id} mono />
        <Stat label="ID" value={user.id} mono />
      </dl>

      <h3 className="mt-2 text-sm font-semibold">Storage</h3>
      {error ? (
        <AlertBanner variant="error" dismissible onDismiss={() => setError(null)}>
          {error}
        </AlertBanner>
      ) : null}
      {loading ? (
        <p className="text-sm text-muted-foreground">
          <RefreshCw className="mr-1 inline size-3.5 animate-spin" /> Loading…
        </p>
      ) : (
        <div className="grid grid-cols-2 gap-3 text-sm">
          <Stat
            label="Storage used"
            value={
              usage
                ? formatBytes(usage.storage_used_bytes) +
                  (quota?.storage_limit_bytes
                    ? ` / ${formatBytes(quota.storage_limit_bytes)}`
                    : "")
                : "—"
            }
          />
          <Stat
            label="Object count"
            value={
              usage
                ? `${usage.object_count}` +
                  (quota?.object_count_limit ? ` / ${quota.object_count_limit}` : "")
                : "—"
            }
          />
          {quota ? (
            <>
              <Stat
                label="Storage usage"
                value={
                  quota.storage_usage_percentage != null
                    ? `${Math.round(quota.storage_usage_percentage)}%`
                    : "—"
                }
              />
              <Stat
                label="Quota state"
                value={quota.quota_exceeded ? "exceeded" : "ok"}
              />
            </>
          ) : (
            <Stat label="Quota" value="No quota set" />
          )}
        </div>
      )}
    </div>
  )
}

function Stat({
  label,
  value,
  mono,
}: {
  label: string
  value: string
  mono?: boolean
}) {
  return (
    <div className="flex flex-col gap-0.5 rounded-md border bg-card/50 px-3 py-2">
      <dt className="text-xs uppercase tracking-wide text-muted-foreground">{label}</dt>
      <dd className={mono ? "truncate font-mono text-xs" : "text-sm"}>{value}</dd>
    </div>
  )
}

function formatBytes(n: number | null | undefined): string {
  if (n == null) return "—"
  const units = ["B", "KB", "MB", "GB", "TB"]
  let i = 0
  let v = n
  while (v >= 1024 && i < units.length - 1) {
    v /= 1024
    i++
  }
  return `${v < 10 ? v.toFixed(1) : Math.round(v)} ${units[i]}`
}

// --- Roles tab ---------------------------------------------------------

function RolesPanel({
  user,
  roles,
  onChanged,
}: {
  user: User
  roles: Role[]
  onChanged: () => Promise<void>
}) {
  const arcadia = useArcadiaClient()
  const [busy, setBusy] = useState<string | null>(null)
  const [error, setError] = useState<string | null>(null)
  const assigned = new Set(user.roles.map((r) => r.id))

  const toggle = useCallback(
    async (role: Role) => {
      setError(null)
      setBusy(role.id)
      try {
        if (assigned.has(role.id)) await removeRole(arcadia, user.id, role.id)
        else await assignRole(arcadia, user.id, role.id)
        await onChanged()
      } catch (err) {
        setError(err instanceof ArcadiaError ? err.message : "Role change failed.")
      } finally {
        setBusy(null)
      }
    },
    [arcadia, assigned, onChanged, user.id],
  )

  return (
    <div className="flex flex-col gap-3 pt-4">
      {error ? (
        <AlertBanner variant="error" dismissible onDismiss={() => setError(null)}>
          {error}
        </AlertBanner>
      ) : null}
      {roles.length === 0 ? (
        <p className="text-sm text-muted-foreground">No roles defined yet.</p>
      ) : (
        <ul className="flex flex-col divide-y rounded-md border">
          {roles.map((r) => {
            const has = assigned.has(r.id)
            return (
              <li
                key={r.id}
                className="flex items-start justify-between gap-3 px-3 py-2"
              >
                <div className="flex flex-col">
                  <span className="flex items-center gap-2 text-sm font-medium">
                    {has ? (
                      <ShieldCheck className="size-4 text-emerald-500" />
                    ) : (
                      <Shield className="size-4 text-muted-foreground" />
                    )}
                    {r.name}{" "}
                    <code className="rounded bg-muted px-1 font-mono text-xs">{r.slug}</code>
                  </span>
                  {r.description ? (
                    <span className="text-xs text-muted-foreground">{r.description}</span>
                  ) : null}
                </div>
                <Button
                  variant={has ? "outline" : "default"}
                  size="sm"
                  disabled={busy !== null}
                  onClick={() => toggle(r)}
                  data-action={`user-${user.id}-role-${r.slug}-${has ? "remove" : "add"}`}
                >
                  {busy === r.id ? (
                    <RefreshCw className="size-3.5 animate-spin" />
                  ) : has ? (
                    <ShieldOff className="size-3.5" />
                  ) : (
                    <Plus className="size-3.5" />
                  )}
                  {has ? "Remove" : "Add"}
                </Button>
              </li>
            )
          })}
        </ul>
      )}
    </div>
  )
}

// --- API keys tab ------------------------------------------------------

function ApiKeysPanel({
  user,
  onChanged,
}: {
  user: User
  onChanged: () => Promise<void>
}) {
  const arcadia = useArcadiaClient()
  const [keys, setKeys] = useState<ApiKey[]>([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [createOpen, setCreateOpen] = useState(false)
  const [revealed, setRevealed] = useState<ApiKeyCreated | null>(null)
  const [pendingRevoke, setPendingRevoke] = useState<ApiKey | null>(null)

  const refresh = useCallback(async () => {
    setLoading(true)
    setError(null)
    try {
      setKeys(await listUserApiKeys(arcadia, user.id))
    } catch (err) {
      setError(err instanceof ArcadiaError ? err.message : "Failed to load keys.")
    } finally {
      setLoading(false)
    }
  }, [arcadia, user.id])

  useEffect(() => {
    refresh()
  }, [refresh])

  return (
    <div className="flex flex-col gap-3 pt-4">
      {error ? (
        <AlertBanner variant="error" dismissible onDismiss={() => setError(null)}>
          {error}
        </AlertBanner>
      ) : null}

      <div className="flex items-center justify-between">
        <p className="text-xs text-muted-foreground">
          Keys are shown in full <strong>only once</strong>, on creation. Treat them like
          passwords.
        </p>
        <Button
          size="sm"
          onClick={() => setCreateOpen(true)}
          data-action={`user-${user.id}-api-key-create`}
        >
          <Plus className="size-4" />
          New key
        </Button>
      </div>

      {loading ? (
        <p className="text-sm text-muted-foreground">
          <RefreshCw className="mr-1 inline size-3.5 animate-spin" /> Loading…
        </p>
      ) : keys.length === 0 ? (
        <p className="rounded-md border bg-muted/30 p-4 text-center text-sm text-muted-foreground">
          No API keys yet.
        </p>
      ) : (
        <ul className="flex flex-col divide-y rounded-md border">
          {keys.map((k) => (
            <li key={k.id} className="flex items-start justify-between gap-3 px-3 py-2">
              <div className="flex flex-col">
                <span className="flex items-center gap-2 font-mono text-xs">
                  <KeyRound className="size-3.5 text-muted-foreground" />
                  {k.key_prefix}…
                  {!k.is_active ? (
                    <Badge variant="secondary" className="ml-1">
                      revoked
                    </Badge>
                  ) : null}
                </span>
                <span className="text-xs text-muted-foreground">
                  {k.description ?? "(no description)"}
                </span>
                <span className="text-[11px] text-muted-foreground">
                  Created {new Date(k.created_at).toLocaleDateString()}
                  {k.last_used_at
                    ? ` · last used ${new Date(k.last_used_at).toLocaleDateString()}`
                    : " · never used"}
                  {k.expires_at
                    ? ` · expires ${new Date(k.expires_at).toLocaleDateString()}`
                    : ""}
                </span>
              </div>
              {k.is_active ? (
                <Button
                  variant="ghost"
                  size="sm"
                  onClick={() => setPendingRevoke(k)}
                  data-action={`user-${user.id}-api-key-${k.id}-revoke`}
                >
                  <Trash2 className="size-3.5" />
                  Revoke
                </Button>
              ) : null}
            </li>
          ))}
        </ul>
      )}

      <CreateApiKeyDialog
        open={createOpen}
        userId={user.id}
        onClose={() => setCreateOpen(false)}
        onCreated={async (created) => {
          setCreateOpen(false)
          setRevealed(created)
          await refresh()
          await onChanged()
        }}
      />

      <RevealKeyDialog created={revealed} onClose={() => setRevealed(null)} />

      <ConfirmDialog
        open={pendingRevoke !== null}
        onOpenChange={(o) => !o && setPendingRevoke(null)}
        title="Revoke API key?"
        description={
          pendingRevoke
            ? `Key ${pendingRevoke.key_prefix}… will stop working immediately. This cannot be undone.`
            : ""
        }
        confirmLabel="Revoke"
        variant="danger"
        onConfirm={async () => {
          if (!pendingRevoke) return
          try {
            await revokeUserApiKey(arcadia, user.id, pendingRevoke.id)
            setPendingRevoke(null)
            await refresh()
          } catch (err) {
            setError(err instanceof ArcadiaError ? err.message : "Revoke failed.")
            setPendingRevoke(null)
          }
        }}
      />
    </div>
  )
}

function CreateApiKeyDialog({
  open,
  userId,
  onClose,
  onCreated,
}: {
  open: boolean
  userId: string
  onClose: () => void
  onCreated: (k: ApiKeyCreated) => Promise<void>
}) {
  const arcadia = useArcadiaClient()
  const [description, setDescription] = useState("")
  const [expiresAt, setExpiresAt] = useState("")
  const [busy, setBusy] = useState(false)
  const [error, setError] = useState<string | null>(null)

  useEffect(() => {
    if (!open) {
      setDescription("")
      setExpiresAt("")
      setError(null)
    }
  }, [open])

  const submit = async () => {
    setError(null)
    setBusy(true)
    try {
      const created = await createUserApiKey(arcadia, userId, {
        description: description || undefined,
        expires_at: expiresAt ? new Date(expiresAt).toISOString() : null,
      })
      await onCreated(created)
    } catch (err) {
      setError(
        err instanceof ArcadiaError
          ? err.message
          : err instanceof Error
            ? err.message
            : "Create failed.",
      )
    } finally {
      setBusy(false)
    }
  }

  return (
    <Dialog open={open} onOpenChange={(o) => !o && onClose()}>
      <DialogContent className="sm:max-w-md">
        <DialogHeader>
          <DialogTitle>New API key</DialogTitle>
          <DialogDescription>
            The full key value will be shown once after creation. Copy it then; we don't store it
            in cleartext.
          </DialogDescription>
        </DialogHeader>

        {error ? (
          <AlertBanner variant="error" dismissible onDismiss={() => setError(null)}>
            {error}
          </AlertBanner>
        ) : null}

        <div className="flex flex-col gap-3">
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="apikey-description">Description</Label>
            <Input
              id="apikey-description"
              value={description}
              onChange={(e) => setDescription(e.target.value)}
              placeholder="e.g. CI deploy key"
              data-action="api-key-form-description"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="apikey-expires">Expires at (optional)</Label>
            <Input
              id="apikey-expires"
              type="datetime-local"
              value={expiresAt}
              onChange={(e) => setExpiresAt(e.target.value)}
              data-action="api-key-form-expires"
            />
          </div>
        </div>

        <DialogFooter>
          <Button variant="outline" onClick={onClose} disabled={busy}>
            Cancel
          </Button>
          <Button onClick={submit} disabled={busy} data-action="api-key-form-create">
            {busy ? <RefreshCw className="size-4 animate-spin" /> : <KeyRound className="size-4" />}
            Create
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

function RevealKeyDialog({
  created,
  onClose,
}: {
  created: ApiKeyCreated | null
  onClose: () => void
}) {
  const [copied, setCopied] = useState(false)
  useEffect(() => {
    if (!created) setCopied(false)
  }, [created])

  if (!created) return null

  const copy = async () => {
    try {
      await navigator.clipboard.writeText(created.api_key)
      setCopied(true)
    } catch {
      // ignore
    }
  }

  return (
    <Dialog open onOpenChange={(o) => !o && onClose()}>
      <DialogContent className="sm:max-w-md">
        <DialogHeader>
          <DialogTitle className="flex items-center gap-2">
            <CheckCircle2 className="size-5 text-emerald-500" />
            API key created
          </DialogTitle>
          <DialogDescription>
            <strong>This is the only time the key will be shown.</strong> Copy and store it now.
          </DialogDescription>
        </DialogHeader>

        <div className="flex flex-col gap-2 rounded-md border bg-muted/30 p-3">
          <code className="select-all break-all font-mono text-xs">{created.api_key}</code>
          <div className="flex items-center justify-between text-xs text-muted-foreground">
            <span>
              Prefix: <code className="font-mono">{created.key_prefix}…</code>
              {created.expires_at
                ? ` · expires ${new Date(created.expires_at).toLocaleString()}`
                : " · never expires"}
            </span>
            <Button size="sm" variant="outline" onClick={copy} data-action="api-key-reveal-copy">
              {copied ? <CheckCircle2 className="size-3.5" /> : <Copy className="size-3.5" />}
              {copied ? "Copied" : "Copy"}
            </Button>
          </div>
        </div>

        <p className="text-xs text-muted-foreground">{created.warning}</p>

        <DialogFooter>
          <Button onClick={onClose} data-action="api-key-reveal-close">
            <X className="size-4" />
            Done
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}
