import { useEffect, useSyncExternalStore } from "react";
import { useTranslation } from "react-i18next";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { Activity, Bell, Check, CheckCheck, ExternalLink, Eye, RotateCcw, Search, Trash2, Tv, UserPlus, X } from "lucide-react";
import { api, type AppNotification, type FeedFilters } from "../lib/api";
import { useLiveQuery } from "../lib/useLiveQuery";
import { focusAccessRequestsTab } from "./AdminUsers";
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 } from "../lib/format";
import type { Page } from "../lib/urlState";
import { LEVEL_STYLE } from "./Toaster";

// 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;

function relativeTime(iso: string | null, t: (k: string, o?: any) => string): string {
  if (!iso) return "";
  return relativeFromMs(new Date(iso).getTime(), t);
}

function relativeFromMs(ms: number, t: (k: string, o?: any) => string): string {
  const secs = Math.max(0, Math.round((Date.now() - ms) / 1000));
  // Reuse the bell's relative-time strings (notifications.time.*) — same format, no dupes.
  if (secs < 60) return t("notifications.time.justNow");
  const mins = Math.round(secs / 60);
  if (mins < 60) return t("notifications.time.minutes", { count: mins });
  const hours = Math.round(mins / 60);
  if (hours < 24) return t("notifications.time.hours", { count: hours });
  return t("notifications.time.days", { count: Math.round(hours / 24) });
}

export default function NotificationsPanel({
  filters,
  setFilters,
  setPage,
  onFocusChannel,
}: {
  filters: FeedFilters;
  setFilters: (f: FeedFilters) => void;
  setPage: (p: Page) => void;
  onFocusChannel: (name: string) => void;
}) {
  const { t } = useTranslation();
  const qc = useQueryClient();
  const q = useLiveQuery<{ items: AppNotification[]; total: number }>(
    ["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: ["notifications"] });
    qc.invalidateQueries({ queryKey: ["notif-unread"] });
  };
  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",
      channelId: meta.channelId || undefined,
      channelName: meta.channelName || undefined,
    });
    setPage("feed");
  }
  function revertState(meta: VideoHiddenMeta | VideoWatchedMeta) {
    api
      .setState(meta.videoId, "new")
      .then(() => {
        qc.invalidateQueries({ queryKey: ["feed"] });
        qc.invalidateQueries({ queryKey: ["feed-count"] });
        // Quietly resolve the now-stale notice (no confirmation toast — the change is visible).
        resolveVideo(meta.videoId);
      })
      .catch(() => {});
  }

  return (
    <div className="p-4 max-w-3xl w-full mx-auto space-y-4">
      <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>

      {q.isLoading && !q.data && clientItems.length === 0 ? (
        <div className="p-8 text-muted text-center">{t("common.loading")}</div>
      ) : !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: (k: string, o?: any) => string;
  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) {
    title = t("inbox.maintenance.title");
    body = t("inbox.maintenance.body", { count: n.data!.count });
  } else if (isScheduler) {
    // "<Job label> finished/failed", with the raw result summary as the (technical) body.
    const job = t(`scheduler.jobs.${n.data!.job_id}`, n.data!.job_id);
    title = t(
      n.data!.status === "error" ? "inbox.jobDone.titleError" : "inbox.jobDone.titleOk",
      { job }
    );
    body = n.data!.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, t)}
          </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: (k: string, o?: any) => string;
  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}</div>
        <div className="flex items-center gap-2 mt-0.5">
          <span className="text-[11px] text-muted">{relativeFromMs(n.ts, t)}</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>
  );
}
