import { useEffect, useSyncExternalStore } from "react";
import { useTranslation } from "react-i18next";
import type { TFunction } from "i18next";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { qk } from "../lib/queryKeys";
import {
  Activity,
  Bell,
  Check,
  CheckCheck,
  ExternalLink,
  Eye,
  RotateCcw,
  Search,
  Trash2,
  Tv,
  UserPlus,
  X,
} from "lucide-react";
import { api, type AppNotification } from "../lib/api";
import { useLiveQuery } from "../lib/useLiveQuery";
import { focusAccessRequestsTab } from "../lib/adminUsersTab";
import {
  clearAll as clearClient,
  dismiss as dismissClient,
  getNotifications,
  getUnreadCount,
  markAllRead as markAllClientRead,
  remove as removeClient,
  resolveVideo,
  subscribe,
  type Notification as ClientNotif,
  type ChannelSubscribedMeta,
  type VideoHiddenMeta,
  type VideoWatchedMeta,
} from "../lib/notifications";
import { channelYouTubeUrl, relativeFromMs, relativeTime } from "../lib/format";
import { LEVEL_STYLE } from "./Toaster";
import { PageToolbar } from "./PageShell";
import { LoadingState, StateMessage } from "./QueryState";
import { useNavigationActions } from "./NavigationProvider";
import { useChannelsActions } from "./ChannelsProvider";
import { useFeedFilters, useFeedFiltersActions } from "./FeedFiltersProvider";

// The single notification center. "System" = durable, server-backed notifications (inbox
// phase 1). "Activity" = the client-side transient events (action confirmations, errors, with
// their inline undo/find actions) that used to live behind a separate bell — folded in here so
// there's one indicator and one place to look. Transient toasts still pop via the Toaster.
const POLL_MS = 8000;

export default function NotificationsPanel() {
  const { t } = useTranslation();
  const { setPage } = useNavigationActions();
  const filters = useFeedFilters();
  const { setFilters } = useFeedFiltersActions();
  // "Focus this channel in the manager" (from a new-subscription notification) lives in
  // ChannelsProvider — read it here and pass it down to the notification rows.
  const { focusChannel: onFocusChannel } = useChannelsActions();
  const qc = useQueryClient();
  const q = useLiveQuery<{ items: AppNotification[]; total: number }>(
    qk.notifications(),
    () => api.notifications(),
    { intervalMs: POLL_MS },
  );
  const serverItems = q.data?.items ?? [];
  const clientItems = useSyncExternalStore(subscribe, getNotifications, getNotifications);
  const clientUnread = useSyncExternalStore(subscribe, getUnreadCount, getUnreadCount);

  // Opening the center marks the (ephemeral) client items read, matching the old bell. Server
  // items stay unread until acted on individually.
  useEffect(() => {
    markAllClientRead();
  }, []);

  const refresh = () => {
    qc.invalidateQueries({ queryKey: qk.notifications() });
    qc.invalidateQueries({ queryKey: qk.notifUnread() });
  };
  const readMut = useMutation({ mutationFn: api.markNotificationRead, onSuccess: refresh });
  const readAllMut = useMutation({ mutationFn: api.markAllNotificationsRead, onSuccess: refresh });
  const dismissMut = useMutation({ mutationFn: api.dismissNotification, onSuccess: refresh });
  const clearMut = useMutation({ mutationFn: api.clearNotifications, onSuccess: refresh });

  const serverUnread = serverItems.some((n) => !n.read);
  const hasAny = serverItems.length > 0 || clientItems.length > 0;
  const hasUnread = serverUnread || clientUnread > 0;

  function markAll() {
    markAllClientRead();
    if (serverUnread) readAllMut.mutate();
    else refresh();
  }
  function clearEverything() {
    clearClient();
    clearMut.mutate();
  }

  // "Find in feed" / undo, ported from the old bell. Works for both hidden and watched
  // notices — jump to the feed filtered to that state and the video's channel.
  function locate(meta: VideoHiddenMeta | VideoWatchedMeta) {
    setFilters({
      ...filters,
      show: meta.kind === "video-hidden" ? "hidden" : "watched",
      channelIds: meta.channelId ? [meta.channelId] : [],
    });
    setPage("feed");
  }
  function revertState(meta: VideoHiddenMeta | VideoWatchedMeta) {
    api
      .setState(meta.videoId, "new")
      .then(() => {
        qc.invalidateQueries({ queryKey: qk.feed() });
        qc.invalidateQueries({ queryKey: qk.feedCount() });
        // Quietly resolve the now-stale notice (no confirmation toast — the change is visible).
        resolveVideo(meta.videoId);
      })
      .catch(() => {});
  }

  return (
    <>
      {/* The inbox header (title + mark-all / clear-all) is fixed chrome; only the list scrolls. */}
      <PageToolbar>
        <div className="px-4 pt-3 max-w-3xl w-full mx-auto">
          <div className="glass rounded-2xl p-4 flex items-center gap-3">
            <Bell className="w-5 h-5 text-accent shrink-0" />
            <div className="flex-1 min-w-0">
              <div className="font-semibold">{t("inbox.title")}</div>
              <div className="text-xs text-muted">{t("inbox.subtitle")}</div>
            </div>
            {hasAny && (
              <div className="flex items-center gap-2">
                <button
                  onClick={markAll}
                  disabled={!hasUnread || readAllMut.isPending}
                  className="inline-flex items-center gap-1.5 text-xs px-2.5 py-1.5 rounded-lg text-muted hover:text-fg hover:bg-card transition disabled:opacity-40"
                >
                  <CheckCheck className="w-4 h-4" />
                  {t("inbox.markAllRead")}
                </button>
                <button
                  onClick={clearEverything}
                  disabled={clearMut.isPending}
                  className="inline-flex items-center gap-1.5 text-xs px-2.5 py-1.5 rounded-lg text-muted hover:text-fg hover:bg-card transition disabled:opacity-40"
                >
                  <Trash2 className="w-4 h-4" />
                  {t("inbox.clearAll")}
                </button>
              </div>
            )}
          </div>
        </div>
      </PageToolbar>

      <div className="px-4 pb-4 pt-3 max-w-3xl w-full mx-auto space-y-4">
        {q.isError && !hasAny ? (
          <StateMessage tone="error" onRetry={() => q.refetch()}>
            {t("common.loadError")}
          </StateMessage>
        ) : q.isLoading && !q.data && clientItems.length === 0 ? (
          <LoadingState />
        ) : !hasAny ? (
          <div className="glass rounded-2xl p-10 text-center text-muted">
            <Bell className="w-8 h-8 mx-auto mb-3 opacity-40" />
            {t("inbox.empty")}
          </div>
        ) : (
          <div className="space-y-4">
            {serverItems.length > 0 && (
              <section>
                <div className="text-xs uppercase tracking-wide text-muted mb-2">
                  {t("inbox.sectionSystem")}
                </div>
                <div className="flex flex-col gap-2">
                  {serverItems.map((n) => (
                    <NotificationRow
                      key={n.id}
                      n={n}
                      t={t}
                      onRead={() => readMut.mutate(n.id)}
                      onDismiss={() => dismissMut.mutate(n.id)}
                      onOpenScheduler={() => setPage("scheduler")}
                    />
                  ))}
                </div>
              </section>
            )}
            {clientItems.length > 0 && (
              <section>
                <div className="text-xs uppercase tracking-wide text-muted mb-2">
                  {t("inbox.sectionActivity")}
                </div>
                <div className="flex flex-col gap-2">
                  {clientItems.map((n) => (
                    <ClientActivityRow
                      key={n.id}
                      n={n}
                      t={t}
                      onFind={locate}
                      onRevert={revertState}
                      onFocusChannel={onFocusChannel}
                      onReviewAccess={() => {
                        focusAccessRequestsTab();
                        setPage("users");
                      }}
                      onRemove={() => removeClient(n.id)}
                    />
                  ))}
                </div>
              </section>
            )}
          </div>
        )}
      </div>
    </>
  );
}

function NotificationRow({
  n,
  t,
  onRead,
  onDismiss,
  onOpenScheduler,
}: {
  n: AppNotification;
  t: TFunction;
  onRead: () => void;
  onDismiss: () => void;
  onOpenScheduler: () => void;
}) {
  // The maintenance producer ships the affected videos in `data.videos`; list a few titles.
  const videos: { id: string; title: string | null }[] = Array.isArray(n.data?.videos)
    ? n.data!.videos
    : [];
  // Known notification types are rendered from i18n (so they're trilingual) using the typed
  // payload; the server-stored title/body are an English fallback for any unknown type.
  const isMaintenance = n.type === "maintenance" && typeof n.data?.count === "number";
  const isScheduler = n.type === "scheduler" && typeof n.data?.job_id === "string";
  let title = n.title;
  let body = n.body;
  if (isMaintenance) {
    // Guarded above (typeof count === "number"); shape the JSON `data` bag for typed access.
    const d = n.data as { count: number };
    title = t("inbox.maintenance.title");
    body = t("inbox.maintenance.body", { count: d.count });
  } else if (isScheduler) {
    // "<Job label> finished/failed", with the raw result summary as the (technical) body.
    const d = n.data as { job_id: string; status?: string; summary?: string };
    const job = t(`scheduler.jobs.${d.job_id}`, d.job_id);
    title = t(d.status === "error" ? "inbox.jobDone.titleError" : "inbox.jobDone.titleOk", {
      job,
    });
    body = d.summary || n.body;
  }
  return (
    <div
      className={`glass rounded-xl p-3.5 flex items-start gap-3 transition ${
        n.read ? "opacity-70" : ""
      }`}
    >
      {!n.read && <span className="mt-1.5 w-2 h-2 rounded-full bg-accent shrink-0" aria-hidden />}
      <div className="flex-1 min-w-0">
        <div className="flex items-baseline gap-2">
          <span className="font-medium text-sm">{title}</span>
          <span className="text-[11px] text-muted shrink-0">{relativeTime(n.created_at)}</span>
        </div>
        {body && <div className="text-sm text-muted mt-0.5">{body}</div>}
        {videos.length > 0 && (
          <ul className="mt-2 text-xs text-muted list-disc pl-4 space-y-0.5">
            {videos.slice(0, 8).map((v) => (
              <li key={v.id} className="truncate">
                {v.title || v.id}
              </li>
            ))}
            {videos.length > 8 && (
              <li className="list-none text-muted/70">
                {t("inbox.andMore", { count: videos.length - 8 })}
              </li>
            )}
          </ul>
        )}
        {isScheduler && (
          <button
            onClick={onOpenScheduler}
            className="flex items-center gap-1 text-accent text-sm font-semibold hover:underline mt-1.5"
          >
            <Activity className="w-3.5 h-3.5" />
            {t("inbox.openScheduler")}
          </button>
        )}
      </div>
      <div className="flex items-center gap-1 shrink-0">
        {!n.read && (
          <button
            onClick={onRead}
            title={t("inbox.markRead")}
            aria-label={t("inbox.markRead")}
            className="p-1.5 rounded-lg text-muted hover:text-fg hover:bg-card transition"
          >
            <Check className="w-4 h-4" />
          </button>
        )}
        <button
          onClick={onDismiss}
          title={t("inbox.dismiss")}
          aria-label={t("inbox.dismiss")}
          className="p-1.5 rounded-lg text-muted hover:text-fg hover:bg-card transition"
        >
          <X className="w-4 h-4" />
        </button>
      </div>
    </div>
  );
}

function ClientActivityRow({
  n,
  t,
  onFind,
  onRevert,
  onFocusChannel,
  onReviewAccess,
  onRemove,
}: {
  n: ClientNotif;
  t: TFunction;
  onFind: (meta: VideoHiddenMeta | VideoWatchedMeta) => void;
  onRevert: (meta: VideoHiddenMeta | VideoWatchedMeta) => void;
  onFocusChannel: (name: string) => void;
  onReviewAccess: () => void;
  onRemove: () => void;
}) {
  const { icon: Icon, color } = LEVEL_STYLE[n.level];
  const needsAction = n.requiresInteraction && !n.dismissed;
  const hidden = n.meta?.kind === "video-hidden" ? n.meta : null;
  const watched = n.meta?.kind === "video-watched" ? n.meta : null;
  const subscribed: ChannelSubscribedMeta | null =
    n.meta?.kind === "channel-subscribed" ? n.meta : null;
  const access = n.meta?.kind === "access-requests" ? n.meta : null;
  return (
    <div
      className={`glass rounded-xl p-3.5 flex items-start gap-3 ${needsAction ? "ring-1 ring-accent/30" : ""}`}
    >
      <Icon className={`w-4 h-4 shrink-0 mt-0.5 ${color}`} />
      <div className="min-w-0 flex-1">
        {n.title && <div className="text-sm font-semibold">{n.title}</div>}
        <div className="text-sm break-words">
          {n.message}
          {n.repeat > 1 && (
            <span className="ml-1.5 align-middle text-[11px] font-semibold text-muted tabular-nums">
              ×{n.repeat}
            </span>
          )}
        </div>
        <div className="flex items-center gap-2 mt-0.5">
          <span className="text-[11px] text-muted">{relativeFromMs(n.ts)}</span>
          {needsAction && (
            <span className="text-[10px] uppercase tracking-wide font-semibold text-accent">
              {t("notifications.actionNeeded")}
            </span>
          )}
        </div>
        {hidden ? (
          <div className="flex items-center gap-3 mt-1">
            <button
              onClick={() => onFind(hidden)}
              className="flex items-center gap-1 text-accent text-sm font-semibold hover:underline"
            >
              <Search className="w-3.5 h-3.5" />
              {t("notifications.findInFeed")}
            </button>
            <button
              onClick={() => onRevert(hidden)}
              className="flex items-center gap-1 text-accent text-sm font-semibold hover:underline"
            >
              <Eye className="w-3.5 h-3.5" />
              {t("notifications.unhide")}
            </button>
          </div>
        ) : watched ? (
          <div className="flex items-center gap-3 mt-1">
            <button
              onClick={() => onFind(watched)}
              className="flex items-center gap-1 text-accent text-sm font-semibold hover:underline"
            >
              <Search className="w-3.5 h-3.5" />
              {t("notifications.findInFeed")}
            </button>
            <button
              onClick={() => onRevert(watched)}
              className="flex items-center gap-1 text-accent text-sm font-semibold hover:underline"
            >
              <RotateCcw className="w-3.5 h-3.5" />
              {t("notifications.unwatch")}
            </button>
          </div>
        ) : subscribed ? (
          <div className="flex items-center gap-3 mt-1">
            <button
              onClick={() => onFocusChannel(subscribed.channelName)}
              className="flex items-center gap-1 text-accent text-sm font-semibold hover:underline"
            >
              <Tv className="w-3.5 h-3.5" />
              {t("notifications.openInManager")}
            </button>
            <a
              href={channelYouTubeUrl(subscribed.channelId)}
              target="_blank"
              rel="noopener noreferrer"
              className="flex items-center gap-1 text-accent text-sm font-semibold hover:underline"
            >
              <ExternalLink className="w-3.5 h-3.5" />
              {t("notifications.openOnYouTube")}
            </a>
          </div>
        ) : access ? (
          <button
            onClick={() => onReviewAccess()}
            className="flex items-center gap-1 text-accent text-sm font-semibold hover:underline mt-1"
          >
            <UserPlus className="w-3.5 h-3.5" />
            {t("common.accessRequestsReview")}
          </button>
        ) : (
          n.action &&
          !n.dismissed && (
            <button
              onClick={() => {
                n.action!.onClick();
                dismissClient(n.id);
              }}
              className="mt-1 text-accent text-sm font-semibold hover:underline"
            >
              {n.action.label}
            </button>
          )
        )}
      </div>
      <button
        onClick={onRemove}
        title={t("inbox.dismiss")}
        aria-label={t("inbox.dismiss")}
        className="p-1.5 rounded-lg text-muted hover:text-fg hover:bg-card transition shrink-0"
      >
        <X className="w-4 h-4" />
      </button>
    </div>
  );
}
