import { useEffect, useState } from "react";
import { Link } from "react-router";

import { API_URL } from "../config/api.ts";
import { useAuth } from "../hooks/useAuth.ts";
import { ErrorCard } from "../components/ErrorCard.tsx";
import { useWS } from "../hooks/useWS.ts";
import type {
  DumpUpvotedData,
  Notification,
  NotificationData,
  PaginatedData,
  PlaylistDumpAddedData,
  PlaylistFollowedData,
  RawNotification,
  UserDumpPostedData,
  UserFollowedData,
} from "../model.ts";
import { deserializeNotification } from "../model.ts";
import { PageShell } from "../components/PageShell.tsx";
import { friendlyFetchError } from "../utils/apiError.ts";

const PAGE_SIZE = 30;

type State =
  | { status: "loading" }
  | { status: "error"; error: string }
  | {
    status: "loaded";
    items: Notification[];
    hasMore: boolean;
    page: number;
    loadingMore: boolean;
  };

type NotifIconKind = "upvote" | "follow" | "dump" | "playlist";

function notifIconKind(type: Notification["type"]): NotifIconKind {
  switch (type) {
    case "dump_upvoted":
      return "upvote";
    case "playlist_followed":
      return "follow";
    case "user_followed":
      return "follow";
    case "user_dump_posted":
      return "dump";
    case "playlist_dump_added":
      return "playlist";
  }
}

function NotifIcon({ type }: { type: Notification["type"] }) {
  const kind = notifIconKind(type);
  const glyphs: Record<NotifIconKind, string> = {
    upvote: "▲",
    follow: "►",
    dump: "🚚",
    playlist: "📜",
  };
  return (
    <span className={`notif-icon notif-icon--${kind}`}>
      {glyphs[kind]}
    </span>
  );
}

function notificationContent(n: Notification): React.ReactNode {
  const data = n.data as NotificationData;
  switch (n.type) {
    case "user_followed": {
      const d = data as UserFollowedData;
      return (
        <>
          <Link to={`/users/${d.followerUsername}`} className="notif-link">
            {d.followerUsername}
          </Link>
          {" started following you"}
        </>
      );
    }
    case "playlist_followed": {
      const d = data as PlaylistFollowedData;
      return (
        <>
          <Link to={`/users/${d.followerUsername}`} className="notif-link">
            {d.followerUsername}
          </Link>
          {" followed your playlist "}
          <Link to={`/playlists/${d.playlistId}`} className="notif-link">
            {d.playlistTitle}
          </Link>
        </>
      );
    }
    case "user_dump_posted": {
      const d = data as UserDumpPostedData;
      return (
        <>
          <Link to={`/users/${d.dumperUsername}`} className="notif-link">
            {d.dumperUsername}
          </Link>
          {" posted "}
          <Link to={`/dumps/${d.dumpId}`} className="notif-link">
            {d.dumpTitle}
          </Link>
        </>
      );
    }
    case "playlist_dump_added": {
      const d = data as PlaylistDumpAddedData;
      return (
        <>
          <Link to={`/dumps/${d.dumpId}`} className="notif-link">
            {d.dumpTitle}
          </Link>
          {" was added to "}
          <Link to={`/playlists/${d.playlistId}`} className="notif-link">
            {d.playlistTitle}
          </Link>
        </>
      );
    }
    case "dump_upvoted": {
      const d = data as DumpUpvotedData;
      return (
        <>
          <Link to={`/users/${d.voterUsername}`} className="notif-link">
            {d.voterUsername}
          </Link>
          {" upvoted "}
          <Link to={`/dumps/${d.dumpId}`} className="notif-link">
            {d.dumpTitle}
          </Link>
        </>
      );
    }
    default:
      return "New notification";
  }
}

function timeAgo(date: Date): string {
  const secs = Math.floor((Date.now() - date.getTime()) / 1000);
  if (secs < 60) return "just now";
  const mins = Math.floor(secs / 60);
  if (mins < 60) return `${mins}m ago`;
  const hrs = Math.floor(mins / 60);
  if (hrs < 24) return `${hrs}h ago`;
  const days = Math.floor(hrs / 24);
  if (days < 7) return `${days}d ago`;
  return date.toLocaleDateString(undefined, { month: "short", day: "numeric" });
}

function startOfDay(d: Date): number {
  return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
}

function groupByDate(
  items: Notification[],
): { label: string; items: Notification[] }[] {
  const todayTs = startOfDay(new Date());
  const yesterdayTs = todayTs - 86_400_000;

  const buckets: Record<string, Notification[]> = {};
  for (const n of items) {
    const ts = startOfDay(n.createdAt);
    const key = ts >= todayTs
      ? "Today"
      : ts >= yesterdayTs
      ? "Yesterday"
      : "Earlier";
    (buckets[key] ??= []).push(n);
  }

  return (["Today", "Yesterday", "Earlier"] as const)
    .filter((k) => buckets[k]?.length)
    .map((label) => ({ label, items: buckets[label] }));
}

export function Notifications() {
  const { authFetch } = useAuth();
  const { clearUnreadNotifications, lastNotification } = useWS();
  const [state, setState] = useState<State>({ status: "loading" });

  useEffect(() => {
    // 1. Fetch with original read state so unread items are highlighted
    // 2. Only after displaying, mark all read on the server
    authFetch(`${API_URL}/api/notifications?page=1&limit=${PAGE_SIZE}`)
      .then((r) => r.json())
      .then((body) => {
        if (!body.success) throw new Error("Failed to load");
        const data = body.data as PaginatedData<RawNotification>;
        setState({
          status: "loaded",
          items: data.items.map(deserializeNotification),
          hasMore: data.hasMore,
          page: 1,
          loadingMore: false,
        });
        // Mark read server-side after we've shown the unread state
        return authFetch(`${API_URL}/api/notifications/read-all`, {
          method: "POST",
        });
      })
      .then(() => {
        clearUnreadNotifications();
        setState((s) =>
          s.status === "loaded"
            ? { ...s, items: s.items.map((n) => ({ ...n, read: true })) }
            : s
        );
      })
      .catch((err) => {
        if (err instanceof TypeError) {
          setState({ status: "error", error: friendlyFetchError(err) });
        } else if (err instanceof Error && err.message === "Failed to load") {
          setState({ status: "error", error: err.message });
        }
      });
  }, []);

  useEffect(() => {
    if (!lastNotification) return;
    setState((s) => {
      if (s.status !== "loaded") return s;
      if (s.items.some((n) => n.id === lastNotification.id)) return s;
      // Keep as unread so it gets highlighted when it arrives
      return { ...s, items: [lastNotification, ...s.items] };
    });
  }, [lastNotification]);

  const loadMore = () => {
    if (state.status !== "loaded" || !state.hasMore || state.loadingMore) {
      return;
    }
    const nextPage = state.page + 1;
    setState((s) => s.status === "loaded" ? { ...s, loadingMore: true } : s);
    authFetch(
      `${API_URL}/api/notifications?page=${nextPage}&limit=${PAGE_SIZE}`,
    )
      .then((r) => r.json())
      .then((body) => {
        const data = body.data as PaginatedData<RawNotification>;
        setState((s) =>
          s.status === "loaded"
            ? {
              ...s,
              items: [...s.items, ...data.items.map(deserializeNotification)],
              hasMore: data.hasMore,
              page: nextPage,
              loadingMore: false,
            }
            : s
        );
      })
      .catch(() =>
        setState((s) =>
          s.status === "loaded" ? { ...s, loadingMore: false } : s
        )
      );
  };

  const totalUnread = state.status === "loaded"
    ? state.items.filter((n) => !n.read).length
    : 0;

  return (
    <PageShell>
      <div className="notifications-page">
        <div className="notifications-header">
          <h1 className="notifications-title">
            <span className="notifications-title-bell">🔔</span>
            Notifications
          </h1>
          {state.status === "loaded" && totalUnread > 0 && (
            <span className="notifications-unread-pill">
              {totalUnread} new
            </span>
          )}
        </div>

        {state.status === "loading" && <p className="page-loading">Loading…</p>}
        {state.status === "error" && (
          <ErrorCard title="Failed to load" message={state.error} />
        )}

        {state.status === "loaded" && state.items.length === 0 && (
          <div className="notifications-empty">
            <span className="notifications-empty-icon">🔕</span>
            <p>Nothing here yet.</p>
            <p className="notifications-empty-hint">
              You'll be notified when someone follows your playlists, upvotes
              your dumps, or posts new content.
            </p>
          </div>
        )}

        {state.status === "loaded" && state.items.length > 0 &&
          groupByDate(state.items).map(({ label, items }) => (
            <section key={label} className="notif-group">
              <h2 className="notif-group-label">{label}</h2>
              <ul className="notification-list">
                {items.map((n) => (
                  <li
                    key={n.id}
                    className={`notification-item${
                      !n.read ? " notification-item--unread" : ""
                    }`}
                  >
                    <NotifIcon type={n.type} />
                    <div className="notification-body">
                      <span className="notification-content">
                        {notificationContent(n)}
                      </span>
                      <time
                        className="notification-time"
                        dateTime={n.createdAt.toISOString()}
                      >
                        {timeAgo(n.createdAt)}
                      </time>
                    </div>
                    {!n.read && (
                      <span className="notif-dot" aria-hidden="true" />
                    )}
                  </li>
                ))}
              </ul>
            </section>
          ))}

        {state.status === "loaded" && state.hasMore && (
          <button
            type="button"
            className="load-more-btn"
            onClick={loadMore}
            disabled={state.loadingMore}
          >
            {state.loadingMore ? "Loading…" : "Load more"}
          </button>
        )}
      </div>
    </PageShell>
  );
}
