import { useAtomValue, useSetAtom } from 'jotai';
import React, { ReactNode, useCallback, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { RoomEvent, RoomEventHandlerMap } from 'matrix-js-sdk';
import { roomToUnreadAtom, unreadEqual, unreadInfoToUnread } from '../../state/room/roomToUnread';
import LogoSVG from '../../../../public/res/lotus.png';
import LogoUnreadSVG from '../../../../public/res/lotus-unread.png';
import LogoHighlightSVG from '../../../../public/res/lotus-highlight.png';
import NotificationSound from '../../../../public/sound/notification.ogg';
import InviteSound from '../../../../public/sound/invite.ogg';
import { notificationPermission, setFavicon } from '../../utils/dom';
import { NOTIFICATION_SOUND_MAP } from '../../utils/notificationSounds';
import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
import { allInvitesAtom } from '../../state/room-list/inviteList';
import { usePreviousValue } from '../../hooks/usePreviousValue';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { getDirectRoomPath, getHomeRoomPath, getInboxInvitesPath } from '../pathUtils';
import { mDirectAtom } from '../../state/mDirectList';
import {
  getMemberDisplayName,
  getNotificationType,
  getUnreadInfo,
  isNotificationEvent,
} from '../../utils/room';
import { NotificationType, UnreadInfo } from '../../../types/matrix/room';
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
import { useSelectedRoom } from '../../hooks/router/useSelectedRoom';
import { useInboxNotificationsSelected } from '../../hooks/router/useInbox';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { usePresenceUpdater } from '../../hooks/usePresenceUpdater';
import { useDeepLinkNavigate } from '../../hooks/useDeepLinkNavigate';
import { toastQueueAtom } from '../../state/toast';
import { useReminders } from '../../hooks/useReminders';
import { useTauriUpdater } from '../../hooks/useTauriUpdater';

function isInQuietHours(start: string, end: string): boolean {
  const now = new Date();
  const [sh, sm] = start.split(':').map(Number);
  const [eh, em] = end.split(':').map(Number);
  const cur = now.getHours() * 60 + now.getMinutes();
  const s = sh! * 60 + (sm ?? 0);
  const e = eh! * 60 + (em ?? 0);
  // start===end means zero-length window → treat as disabled (no quiet hours)
  if (s === e) return false;
  return s < e ? cur >= s && cur < e : cur >= s || cur < e;
}

function SystemEmojiFeature() {
  const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');

  if (twitterEmoji) {
    document.documentElement.style.setProperty('--font-emoji', 'Twemoji');
  } else {
    document.documentElement.style.setProperty('--font-emoji', 'Twemoji_DISABLED');
  }

  return null;
}

function PageZoomFeature() {
  const [pageZoom] = useSetting(settingsAtom, 'pageZoom');

  if (pageZoom === 100) {
    document.documentElement.style.removeProperty('font-size');
  } else {
    document.documentElement.style.setProperty('font-size', `calc(1em * ${pageZoom / 100})`);
  }

  return null;
}

function FaviconUpdater() {
  const roomToUnread = useAtomValue(roomToUnreadAtom);

  useEffect(() => {
    let totalNotif = 0;
    let totalHighlight = 0;
    roomToUnread.forEach((unread) => {
      totalNotif += unread.total;
      totalHighlight += unread.highlight;
    });

    if (totalNotif > 0) {
      setFavicon(totalHighlight > 0 ? LogoHighlightSVG : LogoUnreadSVG);
    } else {
      setFavicon(LogoSVG);
    }

    if (totalHighlight > 0) {
      document.title = `(${totalHighlight}) Lotus Chat`;
    } else if (totalNotif > 0) {
      document.title = `· Lotus Chat`;
    } else {
      document.title = 'Lotus Chat';
    }
  }, [roomToUnread]);

  return null;
}

function InviteNotifications() {
  const audioRef = useRef<HTMLAudioElement>(null);
  const invites = useAtomValue(allInvitesAtom);
  const perviousInviteLen = usePreviousValue(invites.length, 0);
  const mx = useMatrixClient();

  const navigate = useNavigate();
  const [showNotifications] = useSetting(settingsAtom, 'showNotifications');
  const [notificationSound] = useSetting(settingsAtom, 'isNotificationSounds');
  const [quietHoursEnabled] = useSetting(settingsAtom, 'quietHoursEnabled');
  const [quietHoursStart] = useSetting(settingsAtom, 'quietHoursStart');
  const [quietHoursEnd] = useSetting(settingsAtom, 'quietHoursEnd');
  const [inviteSoundId] = useSetting(settingsAtom, 'inviteSoundId');
  const setToast = useSetAtom(toastQueueAtom);

  const soundSrc =
    inviteSoundId !== 'none' ? (NOTIFICATION_SOUND_MAP[inviteSoundId] ?? InviteSound) : null;

  useEffect(() => {
    const el = audioRef.current;
    if (!el) return;
    const source = el.querySelector('source');
    if (source && soundSrc) {
      source.src = soundSrc;
      el.load();
    }
  }, [soundSrc]);

  const notify = useCallback(
    (count: number) => {
      if (document.hasFocus()) {
        setToast({
          id: `invite-${Date.now()}`,
          displayName: 'Invitation',
          body: `You have ${count} new invitation request.`,
          roomName: 'Invites',
          roomId: '',
          hashPath: getInboxInvitesPath(),
        });
        return;
      }

      const noti = new window.Notification('Invitation', {
        icon: LogoSVG,
        badge: LogoSVG,
        body: `You have ${count} new invitation request.`,
        silent: true,
      });

      noti.onclick = () => {
        if (!window.closed) navigate(getInboxInvitesPath());
        noti.close();
      };
    },
    [navigate, setToast],
  );

  const playSound = useCallback(() => {
    const audioElement = audioRef.current;
    audioElement?.play();
  }, []);

  useEffect(() => {
    if (invites.length > perviousInviteLen && mx.getSyncState() === 'SYNCING') {
      const quietActive = quietHoursEnabled && isInQuietHours(quietHoursStart, quietHoursEnd);
      if (!quietActive) {
        if (showNotifications && notificationPermission('granted')) {
          notify(invites.length - perviousInviteLen);
        }

        if (notificationSound && inviteSoundId !== 'none') {
          playSound();
        }
      }
    }
  }, [
    mx,
    invites,
    perviousInviteLen,
    showNotifications,
    notificationSound,
    notify,
    playSound,
    quietHoursEnabled,
    quietHoursStart,
    quietHoursEnd,
    inviteSoundId,
  ]);

  return (
    <audio ref={audioRef} style={{ display: 'none' }}>
      <source src={soundSrc ?? InviteSound} type="audio/ogg" />
    </audio>
  );
}

function PresenceUpdater() {
  usePresenceUpdater();
  return null;
}

function MessageNotifications() {
  const audioRef = useRef<HTMLAudioElement>(null);
  const notifRef = useRef<Notification | undefined>(undefined);
  const unreadCacheRef = useRef<Map<string, UnreadInfo>>(new Map());
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();
  const [showNotifications] = useSetting(settingsAtom, 'showNotifications');
  const [notificationSound] = useSetting(settingsAtom, 'isNotificationSounds');
  const [quietHoursEnabled] = useSetting(settingsAtom, 'quietHoursEnabled');
  const [quietHoursStart] = useSetting(settingsAtom, 'quietHoursStart');
  const [quietHoursEnd] = useSetting(settingsAtom, 'quietHoursEnd');
  const [messageSoundId] = useSetting(settingsAtom, 'messageSoundId');
  const setToast = useSetAtom(toastQueueAtom);
  const mDirects = useAtomValue(mDirectAtom);

  const soundSrc =
    messageSoundId !== 'none'
      ? (NOTIFICATION_SOUND_MAP[messageSoundId] ?? NotificationSound)
      : null;

  useEffect(() => {
    const el = audioRef.current;
    if (!el) return;
    const source = el.querySelector('source');
    if (source && soundSrc) {
      source.src = soundSrc;
      el.load();
    }
  }, [soundSrc]);

  const navigate = useNavigate();
  const notificationSelected = useInboxNotificationsSelected();
  const selectedRoomId = useSelectedRoom();

  const notify = useCallback(
    ({
      roomName,
      roomAvatar,
      username,
      roomId,
      eventId,
      body,
    }: {
      roomName: string;
      roomAvatar?: string;
      username: string;
      roomId: string;
      eventId: string;
      body?: string;
    }) => {
      const roomPath = mDirects.has(roomId)
        ? getDirectRoomPath(roomId, eventId)
        : getHomeRoomPath(roomId, eventId);

      if (document.hasFocus()) {
        setToast({
          id: `${roomId}-${eventId}-${Date.now()}`,
          avatarUrl: roomAvatar,
          displayName: username,
          body: (body ?? '').slice(0, 80),
          roomName,
          roomId,
          hashPath: roomPath,
        });
        return;
      }

      const noti = new window.Notification(roomName, {
        icon: roomAvatar,
        badge: roomAvatar,
        body: body ? `${username}: ${body}`.slice(0, 120) : username,
        silent: true,
      });

      noti.onclick = () => {
        window.focus();
        navigate(roomPath);
        noti.close();
        notifRef.current = undefined;
      };

      notifRef.current?.close();
      notifRef.current = noti;
    },
    [navigate, setToast, mDirects],
  );

  const playSound = useCallback(() => {
    const audioElement = audioRef.current;
    audioElement?.play();
  }, []);

  useEffect(() => {
    const handleTimelineEvent: RoomEventHandlerMap[RoomEvent.Timeline] = (
      mEvent,
      room,
      toStartOfTimeline,
      removed,
      data,
    ) => {
      if (mx.getSyncState() !== 'SYNCING') return;
      if (document.hasFocus() && (selectedRoomId === room?.roomId || notificationSelected)) return;
      if (
        !room ||
        !data.liveEvent ||
        room.isSpaceRoom() ||
        !isNotificationEvent(mEvent) ||
        getNotificationType(mx, room.roomId) === NotificationType.Mute
      ) {
        return;
      }

      const sender = mEvent.getSender();
      const eventId = mEvent.getId();
      if (!sender || !eventId || mEvent.getSender() === mx.getUserId()) return;
      const unreadInfo = getUnreadInfo(room);
      const cachedUnreadInfo = unreadCacheRef.current.get(room.roomId);
      unreadCacheRef.current.set(room.roomId, unreadInfo);

      if (unreadInfo.total === 0) return;
      if (
        cachedUnreadInfo &&
        unreadEqual(unreadInfoToUnread(cachedUnreadInfo), unreadInfoToUnread(unreadInfo))
      ) {
        return;
      }

      const quietActive = quietHoursEnabled && isInQuietHours(quietHoursStart, quietHoursEnd);
      if (!quietActive) {
        if (showNotifications && notificationPermission('granted')) {
          const avatarMxc =
            room.getAvatarFallbackMember()?.getMxcAvatarUrl() ?? room.getMxcAvatarUrl();
          notify({
            roomName: room.name ?? 'Unknown',
            roomAvatar: avatarMxc
              ? (mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined)
              : undefined,
            username: getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender) ?? sender,
            roomId: room.roomId,
            eventId,
            body: (mEvent.getContent().body as string | undefined) ?? '',
          });
        }

        if (notificationSound && messageSoundId !== 'none') {
          playSound();
        }
      }
    };
    mx.on(RoomEvent.Timeline, handleTimelineEvent);
    return () => {
      mx.removeListener(RoomEvent.Timeline, handleTimelineEvent);
    };
  }, [
    mx,
    notificationSound,
    notificationSelected,
    showNotifications,
    playSound,
    notify,
    selectedRoomId,
    useAuthentication,
    quietHoursEnabled,
    quietHoursStart,
    quietHoursEnd,
    messageSoundId,
  ]);

  return (
    <audio ref={audioRef} style={{ display: 'none' }}>
      <source src={soundSrc ?? NotificationSound} type="audio/ogg" />
    </audio>
  );
}

type ClientNonUIFeaturesProps = {
  children: ReactNode;
};

function DeepLinkNavigator() {
  useDeepLinkNavigate();
  return null;
}

function ReminderMonitor() {
  const mx = useMatrixClient();
  const { reminders, removeReminder } = useReminders();
  const setToast = useSetAtom(toastQueueAtom);
  const mDirects = useAtomValue(mDirectAtom);
  const firedRef = useRef<Set<string>>(new Set());

  useEffect(() => {
    const check = () => {
      const now = Date.now();
      reminders.forEach((r) => {
        const key = `${r.eventId}-${r.timestamp}`;
        if (r.timestamp <= now && !firedRef.current.has(key)) {
          firedRef.current.add(key);
          const room = mx.getRoom(r.roomId);
          const hashPath = mDirects.has(r.roomId)
            ? getDirectRoomPath(r.roomId, r.eventId)
            : getHomeRoomPath(r.roomId, r.eventId);
          setToast({
            id: `reminder-${key}`,
            displayName: 'Reminder',
            body: r.message,
            roomName: room?.name ?? 'Unknown Room',
            roomId: r.roomId,
            hashPath,
          });
          removeReminder(r.eventId, r.timestamp);
        }
      });
    };

    check();
    const interval = setInterval(check, 30_000);
    const onVisible = () => {
      if (document.visibilityState === 'visible') check();
    };
    document.addEventListener('visibilitychange', onVisible);
    return () => {
      clearInterval(interval);
      document.removeEventListener('visibilitychange', onVisible);
    };
  }, [mx, reminders, setToast, removeReminder, mDirects]);

  return null;
}

const TAURI_UPDATE_CHECK_INTERVAL = 12 * 60 * 60_000; // 12 hours
const TAURI_UPDATE_LAST_CHECK_KEY = 'lotus.tauriUpdateLastCheck';

function TauriUpdateFeature() {
  const { isTauri, status, check, install } = useTauriUpdater();
  const setToast = useSetAtom(toastQueueAtom);
  const firedRef = useRef<string | null>(null);

  useEffect(() => {
    if (!isTauri) return;

    const runCheck = () => {
      const last = Number(localStorage.getItem(TAURI_UPDATE_LAST_CHECK_KEY) ?? '0');
      if (Date.now() - last < TAURI_UPDATE_CHECK_INTERVAL) return;
      localStorage.setItem(TAURI_UPDATE_LAST_CHECK_KEY, String(Date.now()));
      check();
    };

    runCheck();
    const interval = setInterval(runCheck, TAURI_UPDATE_CHECK_INTERVAL);
    return () => clearInterval(interval);
  }, [isTauri, check]);

  useEffect(() => {
    if (status.state !== 'available') return;
    if (firedRef.current === status.version) return;
    firedRef.current = status.version;
    setToast({
      id: `tauri-update-${status.version}`,
      displayName: '⬆ Update Available',
      body: `Lotus Chat ${status.version} is ready. Click to install and restart.`,
      roomName: 'System',
      roomId: '',
      onClick: install,
      sticky: true,
    });
  }, [status, setToast, install]);

  return null;
}

function LotusDenoiseFeature() {
  const setToast = useSetAtom(toastQueueAtom);

  useEffect(() => {
    const handleMessage = (event: MessageEvent) => {
      if (event.data?.type === 'lotus-denoise-status') {
        const { active, error } = event.data;
        if (!active) {
          setToast({
            id: `denoise-fail-${Date.now()}`,
            displayName: 'Audio Quality',
            body: `ML Noise Suppression failed: ${error || 'Unknown error'}. Falling back to raw mic.`,
            roomName: 'System',
            roomId: '',
          });
        }
      }
    };

    window.addEventListener('message', handleMessage);
    return () => window.removeEventListener('message', handleMessage);
  }, [setToast]);

  return null;
}

export function ClientNonUIFeatures({ children }: ClientNonUIFeaturesProps) {
  return (
    <>
      <SystemEmojiFeature />
      <PageZoomFeature />
      <FaviconUpdater />
      <PresenceUpdater />
      <InviteNotifications />
      <MessageNotifications />
      <ReminderMonitor />
      <TauriUpdateFeature />
      <LotusDenoiseFeature />
      <DeepLinkNavigator />
      {children}
    </>
  );
}
