import { listen } from "@tauri-apps/api/event";
import {
  isPermissionGranted,
  requestPermission,
  sendNotification,
} from "@tauri-apps/plugin-notification";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import {
  getTicketResult,
  listNotifications,
  markAllNotificationsRead,
  markNotificationRead,
} from "../../lib/api";
import type { OrchaiNotification } from "../../lib/types";
import { useLiveRefresh } from "../../lib/useLiveRefresh";
import { buttonClass, cardClass, pillClass } from "../ui/primitives";

type NewNotificationEvent = {
  notification: OrchaiNotification;
};

function shouldSkipSystemNotification(): boolean {
  if (typeof navigator === "undefined") {
    return true;
  }

  // Workaround: tauri-plugin-notification on Linux can panic in Tokio runtime
  // (notify-rust/zbus `block_on` inside async runtime). Keep in-app notifications only.
  return navigator.userAgent.toLowerCase().includes("linux");
}

async function showSystemNotification(notification: OrchaiNotification) {
  if (shouldSkipSystemNotification()) {
    return;
  }

  try {
    let permissionGranted = await isPermissionGranted();
    if (!permissionGranted) {
      const permission = await requestPermission();
      permissionGranted = permission === "granted";
    }

    if (permissionGranted) {
      sendNotification({
        title: notification.title,
        body: notification.message,
      });
    }
  } catch {
    // Best effort only
  }
}

export default function NotificationCenter() {
  const navigate = useNavigate();
  const { projectId, ticketId } = useParams();
  const containerRef = useRef<HTMLDivElement | null>(null);
  const [open, setOpen] = useState(false);
  const [notifications, setNotifications] = useState<OrchaiNotification[]>([]);
  const [ticketProjectId, setTicketProjectId] = useState<string | null>(null);
  const [filter, setFilter] = useState<"all" | "unread" | "errors" | "fixes">(
    "all"
  );
  const scopedProjectId = projectId ?? ticketProjectId;

  useEffect(() => {
    if (projectId) {
      setTicketProjectId(null);
      return;
    }

    if (!ticketId) {
      setTicketProjectId(null);
      return;
    }

    setTicketProjectId(null);
    let cancelled = false;

    void getTicketResult(ticketId)
      .then((result) => {
        if (!cancelled) {
          setTicketProjectId(result.ticket.project_id);
        }
      })
      .catch(() => {
        if (!cancelled) {
          setTicketProjectId(null);
        }
      });

    return () => {
      cancelled = true;
    };
  }, [projectId, ticketId]);

  const loadNotifications = useCallback(async () => {
    if (!scopedProjectId) {
      setNotifications([]);
      return;
    }

    try {
      const items = await listNotifications(scopedProjectId, false);
      setNotifications(items);
    } catch {
      // Ignore load errors in layout chrome
    }
  }, [scopedProjectId]);

  useEffect(() => {
    void loadNotifications();
  }, [loadNotifications]);

  useLiveRefresh({
    enabled: Boolean(scopedProjectId),
    projectId: scopedProjectId,
    refresh: loadNotifications,
    fallbackIntervalMs: 15_000,
  });

  useEffect(() => {
    let cancelled = false;
    let unlisten: (() => void) | null = null;

    const setup = async () => {
      try {
        const cleanup = await listen<NewNotificationEvent>("new-notification", (event) => {
          const incoming = event.payload.notification;

          if (scopedProjectId && incoming.project_id !== scopedProjectId) {
            return;
          }

          setNotifications((prev) => {
            const withoutDuplicate = prev.filter((n) => n.id !== incoming.id);
            return [incoming, ...withoutDuplicate];
          });

          void showSystemNotification(incoming);
        });

        if (cancelled) {
          cleanup();
          return;
        }

        unlisten = cleanup;
      } catch (error: unknown) {
        console.error("Failed to subscribe to notifications", error);
      }
    };

    void setup();

    return () => {
      cancelled = true;
      if (unlisten) {
        unlisten();
      }
    };
  }, [scopedProjectId]);

  useEffect(() => {
    if (!open) {
      return;
    }

    function handleOutsideClick(event: MouseEvent) {
      const target = event.target as Node | null;
      if (!containerRef.current || !target) {
        return;
      }
      if (!containerRef.current.contains(target)) {
        setOpen(false);
      }
    }

    function handleEscape(event: KeyboardEvent) {
      if (event.key === "Escape") {
        setOpen(false);
      }
    }

    document.addEventListener("mousedown", handleOutsideClick);
    document.addEventListener("keydown", handleEscape);
    return () => {
      document.removeEventListener("mousedown", handleOutsideClick);
      document.removeEventListener("keydown", handleEscape);
    };
  }, [open]);

  const unreadCount = useMemo(
    () => notifications.filter((n) => !n.read).length,
    [notifications]
  );

  const filteredNotifications = useMemo(() => {
    switch (filter) {
      case "unread":
        return notifications.filter((n) => !n.read);
      case "errors":
        return notifications.filter((n) => n.notification_type === "Error");
      case "fixes":
        return notifications.filter((n) => n.notification_type === "FixReady");
      default:
        return notifications;
    }
  }, [filter, notifications]);

  async function handleOpenNotification(notification: OrchaiNotification) {
    setOpen(false);

    if (!notification.read) {
      setNotifications((prev) =>
        prev.map((n) => (n.id === notification.id ? { ...n, read: true } : n))
      );

      // Do not block navigation on read acknowledgement.
      void markNotificationRead(notification.id).catch(() => {
        // ignore
      });
    }

    if (notification.ticket_id) {
      navigate(`/tickets/${notification.ticket_id}`);
      return;
    }

    navigate(`/projects/${notification.project_id}`);
  }

  async function handleMarkAllRead() {
    if (!scopedProjectId) {
      return;
    }

    try {
      await markAllNotificationsRead(scopedProjectId);
      setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
    } catch {
      // ignore
    }
  }

  return (
    <div className="relative" ref={containerRef}>
      <button
        type="button"
        onClick={() => setOpen((v) => !v)}
        className={`${buttonClass({ variant: "secondary", size: "sm" })} relative border border-gray-300`}
      >
        Notifications
        {unreadCount > 0 && (
          <span className="ml-2 rounded-full bg-red-600 px-1.5 py-0.5 text-xs font-semibold text-white">
            {unreadCount}
          </span>
        )}
      </button>

      {open && (
        <div className={`absolute right-0 z-20 mt-2 w-[360px] shadow-lg ${cardClass}`}>
          <div className="flex items-center justify-between border-b border-gray-200 px-3 py-2">
            <h3 className="text-sm font-semibold text-gray-800">Notifications</h3>
            <button
              type="button"
              onClick={handleMarkAllRead}
              className={buttonClass({ variant: "ghost", size: "xs" })}
              disabled={!scopedProjectId || unreadCount === 0}
            >
              Mark all read
            </button>
          </div>

          <div className="flex gap-1 border-b border-gray-100 px-2 py-2">
            {[
              { id: "all", label: "All" },
              { id: "unread", label: "Unread" },
              { id: "errors", label: "Errors" },
              { id: "fixes", label: "Fixes" },
            ].map((item) => (
              <button
                key={item.id}
                type="button"
                onClick={() =>
                  setFilter(item.id as "all" | "unread" | "errors" | "fixes")
                }
                className={`${pillClass(filter === item.id)} text-xs`}
              >
                {item.label}
              </button>
            ))}
          </div>

          <div className="max-h-[420px] overflow-y-auto">
            {filteredNotifications.length === 0 ? (
              <div className="px-3 py-6 text-center text-sm text-gray-400">
                No notifications.
              </div>
            ) : (
              filteredNotifications.map((notification) => (
                <button
                  key={notification.id}
                  type="button"
                  onClick={() => void handleOpenNotification(notification)}
                  className={`block w-full border-b border-gray-100 px-3 py-2 text-left hover:bg-gray-50 ${
                    notification.read ? "bg-white" : "bg-blue-50/40"
                  }`}
                >
                  <div className="flex items-center justify-between gap-2">
                    <span className="truncate text-sm font-medium text-gray-900">
                      {notification.title}
                    </span>
                    {!notification.read && (
                      <span className="h-2 w-2 shrink-0 rounded-full bg-blue-600" />
                    )}
                  </div>
                  <p className="mt-0.5 line-clamp-2 text-xs text-gray-600">
                    {notification.message}
                  </p>
                  <p className="mt-1 text-[11px] text-gray-400">
                    {new Date(notification.created_at).toLocaleString()}
                  </p>
                </button>
              ))
            )}
          </div>
        </div>
      )}
    </div>
  );
}
