"use client";

import * as React from "react";
import { Laptop, LogOut, MonitorSmartphone, RefreshCw, Users } from "lucide-react";
import { toast } from "sonner";

import { ErrorBanner } from "@/components/shared/error-banner";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip";
import { requestDashboardApi } from "@/lib/dashboard-api-client";
import { errorMessageOr, toastApiError } from "@/lib/toast-api-error";

import {
  deviceTypeLabel,
  emptySessionFilters,
  formatRelativeUnix,
  formatUnix,
  presenceLabel,
  type SessionListFilters,
  type SessionRow,
} from "./schema";
import { SessionsReauthDialog } from "./sessions-reauth-dialog";

type SessionsCatalogResponse = {
  sessions: SessionRow[];
  total: number;
  onlineWindowSeconds: number;
  idleExpireSeconds?: number;
  errorMessage: string | null;
};

type PendingRevoke =
  | { kind: "one"; row: SessionRow }
  | { kind: "selected"; rows: SessionRow[] };

type SessionsPageClientProps = {
  initialSessions: SessionRow[];
  initialTotal: number;
  initialErrorMessage: string | null;
  onlineWindowSeconds: number;
  idleExpireSeconds: number;
  initialUserId?: number | null;
  initialCustomerId?: number | null;
};

function isSelectableSession(row: SessionRow): boolean {
  return canEndSession(row);
}

function canEndSession(row: SessionRow): boolean {
  return row.sessionStatus === "active" && !row.isCurrent;
}

export function SessionsPageClient({
  initialSessions,
  initialTotal,
  initialErrorMessage,
  onlineWindowSeconds,
  idleExpireSeconds,
  initialUserId = null,
  initialCustomerId = null,
}: SessionsPageClientProps) {
  // The backend has no permission system left: every authenticated staff account may
  // revoke sessions.
  const canRevoke = true;
  const [sessions, setSessions] = React.useState(initialSessions);
  const [total, setTotal] = React.useState(initialTotal);
  const [errorMessage, setErrorMessage] = React.useState(initialErrorMessage);
  const [loading, setLoading] = React.useState(false);
  const [filters, setFilters] = React.useState<SessionListFilters>({
    ...emptySessionFilters,
    userId: initialUserId,
    customerId: initialCustomerId,
  });
  const [searchDraft, setSearchDraft] = React.useState("");
  const [pendingRevoke, setPendingRevoke] = React.useState<PendingRevoke | null>(null);
  const [selectedIds, setSelectedIds] = React.useState<Set<string>>(new Set());

  const selectableSessions = React.useMemo(
    () => (canRevoke ? sessions.filter(isSelectableSession) : []),
    [sessions, canRevoke],
  );
  const selectedRows = React.useMemo(
    () => sessions.filter((row) => selectedIds.has(row.sessionId) && isSelectableSession(row)),
    [sessions, selectedIds],
  );
  const allSelectableSelected =
    selectableSessions.length > 0 &&
    selectableSessions.every((row) => selectedIds.has(row.sessionId));
  const someSelectableSelected = selectedRows.length > 0;

  const fetchCatalog = React.useCallback(async () => {
    const params = new URLSearchParams();
    params.set("status", filters.status);
    if (filters.audience !== "all") params.set("audience", filters.audience);
    if (filters.search) params.set("search", filters.search);
    if (filters.userId) params.set("userId", String(filters.userId));
    if (filters.customerId) params.set("customerId", String(filters.customerId));

    return requestDashboardApi<SessionsCatalogResponse>({
      url: `/dashboard/admins/sessions/catalog?${params.toString()}`,
      method: "GET",
      fallbackError: "Could not load sessions.",
    });
  }, [filters]);

  const applyCatalog = React.useCallback((data: SessionsCatalogResponse) => {
    const nextSessions = data.sessions ?? [];
    setSessions(nextSessions);
    setTotal(data.total ?? 0);
    setErrorMessage(data.errorMessage);
    setSelectedIds((prev) => {
      if (prev.size === 0) return prev;
      const allowed = new Set(
        nextSessions.filter(isSelectableSession).map((row) => row.sessionId),
      );
      const next = new Set<string>();
      for (const id of prev) {
        if (allowed.has(id)) next.add(id);
      }
      return next;
    });
  }, []);

  const applyCatalogFailure = React.useCallback((error: unknown) => {
    setErrorMessage(errorMessageOr(error, "Could not load sessions."));
  }, []);

  // User-driven / polled reload: shows the loading state, fetches, applies.
  const refresh = React.useCallback(async () => {
    setLoading(true);
    setErrorMessage(null);
    try {
      applyCatalog(await fetchCatalog());
    } catch (error) {
      applyCatalogFailure(error);
    } finally {
      setLoading(false);
    }
  }, [applyCatalog, applyCatalogFailure, fetchCatalog]);

  // Reset the loading/error banner whenever the filters change, including the first render (the
  // server-provided list is refetched on mount), then let the effect below fetch. Doing it here
  // keeps the effect free of synchronous state writes.
  const [loadedFilters, setLoadedFilters] = React.useState<SessionListFilters | null>(null);
  if (loadedFilters !== filters) {
    setLoadedFilters(filters);
    setLoading(true);
    setErrorMessage(null);
  }

  // Mount / filter-change fetch. `active` drops a response that arrives after the filters moved on.
  React.useEffect(() => {
    let active = true;
    fetchCatalog()
      .then((data) => {
        if (active) applyCatalog(data);
      })
      .catch((error: unknown) => {
        if (active) applyCatalogFailure(error);
      })
      .finally(() => {
        if (active) setLoading(false);
      });
    return () => {
      active = false;
    };
  }, [applyCatalog, applyCatalogFailure, fetchCatalog]);

  React.useEffect(() => {
    const POLL_MS = 10_000;

    const tick = () => {
      if (document.visibilityState === "hidden") return;
      void refresh();
    };

    const id = window.setInterval(tick, POLL_MS);
    const onFocus = () => void refresh();
    const onVisible = () => {
      if (document.visibilityState === "visible") void refresh();
    };

    window.addEventListener("focus", onFocus);
    document.addEventListener("visibilitychange", onVisible);

    return () => {
      window.clearInterval(id);
      window.removeEventListener("focus", onFocus);
      document.removeEventListener("visibilitychange", onVisible);
    };
  }, [refresh]);

  function toggleRow(sessionId: string, checked: boolean) {
    setSelectedIds((prev) => {
      const next = new Set(prev);
      if (checked) next.add(sessionId);
      else next.delete(sessionId);
      return next;
    });
  }

  function toggleSelectAll(checked: boolean) {
    if (!checked) {
      setSelectedIds(new Set());
      return;
    }
    setSelectedIds(new Set(selectableSessions.map((row) => row.sessionId)));
  }

  function applyOptimisticRevoke(pending: PendingRevoke) {
    const removeIds = new Set(
      pending.kind === "one"
        ? [pending.row.sessionId]
        : pending.rows.map((row) => row.sessionId),
    );
    setSessions((prev) => {
      const next = prev.filter((row) => !removeIds.has(row.sessionId));
      const removed = prev.length - next.length;
      if (removed > 0) {
        setTotal((t) => Math.max(0, t - removed));
      }
      return next;
    });
    setSelectedIds((prev) => {
      const next = new Set(prev);
      for (const id of removeIds) next.delete(id);
      return next;
    });
  }

  async function executePendingRevoke(pending: PendingRevoke) {
    if (!canRevoke) return;
    if (pending.kind === "one") {
      if (!canEndSession(pending.row)) return;
      await requestDashboardApi({
        url: "/dashboard/admins/sessions/revoke",
        method: "POST",
        body: { sessionId: pending.row.sessionId },
        fallbackError: "Could not end session.",
      });
      toast.success("Session ended.");
      applyOptimisticRevoke(pending);
      await refresh();
      return;
    }

    const rows = pending.rows.filter(canEndSession);
    if (rows.length === 0) return;

    let ended = 0;
    let failed = 0;
    const endedRows: SessionRow[] = [];
    for (let index = 0; index < rows.length; index += 1) {
      const row = rows[index];
      const isLast = index === rows.length - 1;
      try {
        await requestDashboardApi({
          url: "/dashboard/admins/sessions/revoke",
          method: "POST",
          body: {
            sessionId: row.sessionId,
            // Keep step-up cookie until the last selected revoke finishes.
            preserveReauth: !isLast,
          },
          fallbackError: "Could not end session.",
        });
        ended += 1;
        endedRows.push(row);
      } catch {
        failed += 1;
      }
    }

    if (endedRows.length > 0) {
      applyOptimisticRevoke({ kind: "selected", rows: endedRows });
    }
    if (ended > 0 && failed === 0) {
      toast.success(ended === 1 ? "Session ended." : `${ended} sessions ended.`);
    } else if (ended > 0) {
      toast.success(`${ended} session(s) ended; ${failed} failed.`);
    } else {
      toast.error("Could not end the selected sessions.");
    }
    await refresh();
  }

  async function handleReauthConfirmed() {
    const pending = pendingRevoke;
    if (!pending) return;
    try {
      await executePendingRevoke(pending);
      setPendingRevoke(null);
    } catch (error) {
      const message = errorMessageOr(error, "Could not end session.");
      if (message.toLowerCase().includes("re-authenticate")) {
        toast.error("Confirm your password or passkey again, then retry.");
        return;
      }
      toastApiError(error, "Could not end session.");
    }
  }

  const reauthDescription =
    pendingRevoke?.kind === "selected"
      ? `Confirm your identity to end ${pendingRevoke.rows.length} selected session(s).`
      : pendingRevoke
        ? `Confirm your identity to end the session for ${pendingRevoke.row.email || pendingRevoke.row.displayName || "this user"}.`
        : undefined;

  return (
    <div className="space-y-4">
      <SessionsReauthDialog
        open={pendingRevoke !== null}
        description={reauthDescription}
        onConfirmed={handleReauthConfirmed}
        onCancel={() => setPendingRevoke(null)}
      />

      <ErrorBanner message={errorMessage} />

      <div className="flex flex-wrap items-end gap-3">
        <div className="space-y-1">
          <Label className="text-xs">Audience</Label>
          <Select
            value={filters.audience}
            onValueChange={(value) =>
              setFilters((prev) => ({
                ...prev,
                audience: value as SessionListFilters["audience"],
              }))
            }
          >
            <SelectTrigger className="h-9 w-[140px]">
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="all">All</SelectItem>
              <SelectItem value="backend">Admin</SelectItem>
              <SelectItem value="frontend">Portal</SelectItem>
            </SelectContent>
          </Select>
        </div>

        <div className="space-y-1">
          <Label className="text-xs">Status</Label>
          <Select
            value={filters.status}
            onValueChange={(value) =>
              setFilters((prev) => ({
                ...prev,
                status: value as SessionListFilters["status"],
              }))
            }
          >
            <SelectTrigger className="h-9 w-[160px]">
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="online">Online now</SelectItem>
              <SelectItem value="active">Session active</SelectItem>
              <SelectItem value="revoked">Revoked</SelectItem>
              <SelectItem value="all">All</SelectItem>
            </SelectContent>
          </Select>
        </div>

        <div className="space-y-1">
          <Label className="text-xs">Search</Label>
          <form
            className="flex gap-2"
            onSubmit={(event) => {
              event.preventDefault();
              setFilters((prev) => ({ ...prev, search: searchDraft.trim() }));
            }}
          >
            <Input
              className="h-9 w-[220px]"
              value={searchDraft}
              onChange={(event) => setSearchDraft(event.target.value)}
              placeholder="Email, name, tenant..."
            />
            <Button type="submit" size="sm" variant="secondary" className="h-9">
              Search
            </Button>
          </form>
        </div>

        <Button
          type="button"
          size="sm"
          variant="outline"
          className="h-9"
          onClick={() => void refresh()}
          disabled={loading}
        >
          <RefreshCw className={`size-4 ${loading ? "animate-spin" : ""}`} />
          Refresh
        </Button>

        {canRevoke && someSelectableSelected ? (
          <Button
            type="button"
            size="sm"
            variant="destructive"
            className="h-9"
            onClick={() => setPendingRevoke({ kind: "selected", rows: selectedRows })}
          >
            <Users className="size-3.5" />
            End all ({selectedRows.length})
          </Button>
        ) : null}
      </div>

      <p className="text-muted-foreground text-xs">
        Online = activity within the last {Math.round(onlineWindowSeconds / 60)} minutes.
        Sessions auto-end after {Math.round(idleExpireSeconds / 60)} minutes with no activity. Showing{" "}
        {sessions.length} of {total} session(s). Auto-refreshes every 10s while this tab is open.
        Ending a session requires password or passkey confirmation each time. Your current session
        cannot be selected or ended.
      </p>

      <div className="rounded-md border">
        <Table>
          <TableHeader>
            <TableRow>
              <TableHead className="w-10">
                {canRevoke && selectableSessions.length > 0 ? (
                  <Checkbox
                    checked={
                      allSelectableSelected
                        ? true
                        : someSelectableSelected
                          ? "indeterminate"
                          : false
                    }
                    onCheckedChange={(value) => toggleSelectAll(value === true)}
                    aria-label="Select all endable sessions"
                  />
                ) : null}
              </TableHead>
              <TableHead>User</TableHead>
              <TableHead>Audience</TableHead>
              <TableHead>Presence</TableHead>
              <TableHead>Device</TableHead>
              <TableHead>OS / Browser</TableHead>
              <TableHead>Location</TableHead>
              <TableHead>IP</TableHead>
              <TableHead>Login time</TableHead>
              <TableHead>Last activity</TableHead>
              <TableHead className="text-right">Actions</TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            {sessions.length === 0 ? (
              <TableRow>
                <TableCell colSpan={11} className="text-muted-foreground h-24 text-center text-sm">
                  No sessions match the current filters.
                </TableCell>
              </TableRow>
            ) : (
              sessions.map((row) => {
                const selectable = canRevoke && isSelectableSession(row);
                return (
                  <TableRow key={row.sessionId} className={row.isCurrent ? "bg-muted/40" : undefined}>
                    <TableCell>
                      {selectable ? (
                        <Checkbox
                          checked={selectedIds.has(row.sessionId)}
                          onCheckedChange={(value) => toggleRow(row.sessionId, value === true)}
                          aria-label={`Select session for ${row.email || row.displayName || row.sessionId}`}
                        />
                      ) : null}
                    </TableCell>
                    <TableCell>
                      <div className="flex items-start gap-2">
                        {row.isCurrent ? (
                          <TooltipProvider>
                            <Tooltip>
                              <TooltipTrigger asChild>
                                <span className="bg-primary text-primary-foreground mt-0.5 inline-flex size-6 shrink-0 items-center justify-center rounded-md">
                                  <MonitorSmartphone className="size-3.5" aria-hidden />
                                  <span className="sr-only">This device</span>
                                </span>
                              </TooltipTrigger>
                              <TooltipContent>This device (current session)</TooltipContent>
                            </Tooltip>
                          </TooltipProvider>
                        ) : null}
                        <div className="flex min-w-0 flex-col">
                          <span className="font-medium">{row.displayName || "-"}</span>
                          <span className="text-muted-foreground text-xs">{row.email || "-"}</span>
                          {row.isCurrent ? (
                            <Badge variant="secondary" className="mt-1 w-fit text-[10px]">
                              This device
                            </Badge>
                          ) : null}
                        </div>
                      </div>
                    </TableCell>
                    <TableCell>
                      <Badge variant="outline">
                        {row.audience === "backend" ? "Admin" : "Portal"}
                      </Badge>
                    </TableCell>
                    <TableCell>
                      <PresenceBadge presence={row.presence} />
                    </TableCell>
                    <TableCell className="text-sm">
                      <div className="flex items-center gap-1.5">
                        <Laptop className="text-muted-foreground size-3.5 shrink-0" />
                        {deviceTypeLabel(row.deviceType)}
                      </div>
                    </TableCell>
                    <TableCell className="text-sm">
                      <div className="flex flex-col">
                        <span>{row.os || "Unknown OS"}</span>
                        <span className="text-muted-foreground text-xs">
                          {row.browser || "Unknown browser"}
                        </span>
                      </div>
                    </TableCell>
                    <TableCell className="text-sm">{row.location || "Unknown location"}</TableCell>
                    <TableCell className="font-mono text-xs">{row.ip || "-"}</TableCell>
                    <TableCell className="text-sm whitespace-nowrap">
                      <div className="flex flex-col">
                        <span suppressHydrationWarning>{formatRelativeUnix(row.createdAt)}</span>
                        <span className="text-muted-foreground text-xs">
                          {formatUnix(row.createdAt)}
                        </span>
                      </div>
                    </TableCell>
                    <TableCell className="text-sm whitespace-nowrap">
                      <div className="flex flex-col">
                        <span suppressHydrationWarning>{formatRelativeUnix(row.lastSeenAt)}</span>
                        <span className="text-muted-foreground text-xs">
                          {formatUnix(row.lastSeenAt)}
                        </span>
                      </div>
                    </TableCell>
                    <TableCell className="text-right">
                      {canRevoke && row.sessionStatus === "active" ? (
                        row.isCurrent ? (
                          <span className="text-muted-foreground text-xs">Cannot end</span>
                        ) : (
                          <Button
                            type="button"
                            size="sm"
                            variant="destructive"
                            onClick={() => setPendingRevoke({ kind: "one", row })}
                          >
                            <LogOut className="size-3.5" />
                            End
                          </Button>
                        )
                      ) : (
                        <span className="text-muted-foreground text-xs">-</span>
                      )}
                    </TableCell>
                  </TableRow>
                );
              })
            )}
          </TableBody>
        </Table>
      </div>
    </div>
  );
}

function PresenceBadge({ presence }: { presence: SessionRow["presence"] }) {
  const variant =
    presence === "online" ? "default" : presence === "session_active" ? "secondary" : "outline";
  return <Badge variant={variant}>{presenceLabel(presence)}</Badge>;
}
