'use client';

import { useState, useEffect, useCallback, useRef } from 'react';
import { usePathname, useRouter } from 'next/navigation';
import Sidebar from './Sidebar';
import Topbar from './Topbar';
import AlertOverlay from '@client/components/AlertOverlay';
import DemoReadOnlyBanner from '@client/components/demo/DemoReadOnlyBanner';
import { LiveCountsProvider, useLiveCounts } from '@client/contexts/LiveCountsContext';
import { PageHeaderProvider, usePageHeader } from '@client/contexts/PageHeaderContext';
import { BrandingProvider } from '@client/contexts/BrandingContext';
import { PlanFeaturesProvider } from '@client/contexts/PlanFeaturesContext';
import { useAuth } from '@client/contexts/AuthContext';
import { useLanguage } from '@client/contexts/LanguageContext';
import type { AlertSettings } from '@client/api/alert-settings';
import { listOrders, updateOrder } from '@client/api/orders';
import { listBookings, updateBooking } from '@client/api/bookings';
import { toast } from 'sonner';

const MAX_BULK_ACCEPT = 5;

interface LiveAlert {
  id: string;
  type: 'order' | 'booking' | 'escalation';
  title: string;
  subtitle: string;
  details: string[];
  timestamp: string;
  countdown: number;
  acceptOrderIds?: string[];
  acceptBookingIds?: string[];
  acceptDisabled?: boolean;
  acceptLabel?: string;
}

const DEFAULT_ALERT_SETTINGS: AlertSettings = {
  soundEnabled: true,
  volume: 70,
  autoDismiss: 30,
  showOrders: true,
  showBookings: true,
  showAiEscalations: true,
  browserNotifications: false,
};

const TRIAL_EXEMPT = ['/billing', '/onboarding', '/sign-out', '/logout'];

function getCookie(name: string): string | null {
  if (typeof document === 'undefined') return null;
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const match = document.cookie.match(new RegExp('(^| )' + escaped + '=([^;]+)'));
  return match ? decodeURIComponent(match[2]) : null;
}

const ORDER_NOTIF_TAG_PREFIX = 'restroagent-order-alert-';
const ORDER_NOTIF_URL = '/order-management';
const BOOKING_NOTIF_TAG_PREFIX = 'restroagent-booking-alert-';
const BOOKING_NOTIF_URL = '/table-booking-management';

function notificationActionsSupported(): boolean {
  if (typeof window === 'undefined') return false;
  if (!('Notification' in window)) return false;
  if (!('serviceWorker' in navigator)) return false;
  const N = window.Notification as typeof Notification & { maxActions?: number };
  if (typeof N.maxActions === 'number') return N.maxActions > 0;
  try {
    return 'actions' in (Notification.prototype as object);
  } catch {
    return false;
  }
}

interface NotificationPayload {
  alertId: string;
  title: string;
  body: string;
  acceptLabel: string;
  tag: string;
  url: string;
  orderIds?: string[];
  bookingIds?: string[];
}

async function showAlertNotification(payload: NotificationPayload): Promise<void> {
  if (typeof window === 'undefined') return;
  if (!('Notification' in window)) return;
  if (Notification.permission !== 'granted') return;

  const { alertId, title, body, acceptLabel, tag, url, orderIds, bookingIds } = payload;
  const data = { alertId, orderIds: orderIds ?? [], bookingIds: bookingIds ?? [], url };
  const hasActionable = (orderIds && orderIds.length > 0) || (bookingIds && bookingIds.length > 0);

  if ('serviceWorker' in navigator && notificationActionsSupported()) {
    try {
      const reg = await navigator.serviceWorker.ready;
      await reg.showNotification(title, {
        body,
        tag,
        renotify: false,
        data,
        ...(hasActionable
          ? { actions: [{ action: 'accept', title: acceptLabel }] }
          : {}),
      } as NotificationOptions);
      return;
    } catch {
      // Fall through to plain Notification below.
    }
  }

  try {
    const notif = new Notification(title, { body, tag });
    notif.onclick = () => {
      try { window.focus(); } catch {}
      notif.close();
    };
  } catch {
    // Notification API may be unavailable in some contexts; ignore.
  }
}

function showOrderNotification(
  alertId: string,
  title: string,
  body: string,
  acceptLabel: string,
  orderIds: string[],
): Promise<void> {
  return showAlertNotification({
    alertId,
    title,
    body,
    acceptLabel,
    tag: `${ORDER_NOTIF_TAG_PREFIX}${alertId}`,
    url: ORDER_NOTIF_URL,
    orderIds,
  });
}

function showBookingNotification(
  alertId: string,
  title: string,
  body: string,
  acceptLabel: string,
  bookingIds: string[],
): Promise<void> {
  return showAlertNotification({
    alertId,
    title,
    body,
    acceptLabel,
    tag: `${BOOKING_NOTIF_TAG_PREFIX}${alertId}`,
    url: BOOKING_NOTIF_URL,
    bookingIds,
  });
}

function AppLayoutInner({ children }: { children: React.ReactNode }) {
  const [isCollapsed, setIsCollapsed] = useState(false);
  const [mobileOpen, setMobileOpen] = useState(false);
  const [isAdminMode, setIsAdminMode] = useState(false);
  const [impersonatingName, setImpersonatingName] = useState<string | null>(null);
  const [impersonatingRestaurantId, setImpersonatingRestaurantId] = useState<string | null>(null);
  const [exitingImpersonation, setExitingImpersonation] = useState(false);
  const pathname = usePathname();
  const router = useRouter();
  const { title: pageTitle, subtitle: pageSubtitle } = usePageHeader();
  const { onAlert, refresh: refreshLiveCounts } = useLiveCounts();
  const { restaurantId, userRecord } = useAuth();
  const [liveAlerts, setLiveAlerts] = useState<LiveAlert[]>([]);
  const alertIdCounter = useRef(0);
  const alertSettingsRef = useRef<AlertSettings>(DEFAULT_ALERT_SETTINGS);
  const liveAlertsRef = useRef<LiveAlert[]>([]);
  useEffect(() => { liveAlertsRef.current = liveAlerts; }, [liveAlerts]);

  useEffect(() => {
    const name = getCookie('impersonating_name');
    const rid = getCookie('impersonating_restaurant_id');
    setImpersonatingName(name);
    setImpersonatingRestaurantId(rid);
  }, [pathname]);

  useEffect(() => {
    if (!restaurantId) return;
    const userRole = userRecord?.role;
    if (userRole === 'superadmin' || userRole === 'support') return;
    if (TRIAL_EXEMPT.some(p => pathname.startsWith(p))) return;
    fetch('/api/billing')
      .then(r => r.ok ? r.json() : null)
      .then(data => {
        const sub = data?.subscription;
        if (!sub) return;
        const isTrialExpired =
          sub.status === 'trial' &&
          sub.trial_end &&
          new Date(sub.trial_end) < new Date();
        const isInactive = sub.status === 'inactive' || sub.status === 'cancelled';
        if (isTrialExpired || isInactive) {
          router.replace('/billing?reason=trial_expired');
        }
      })
      .catch(() => {});
  }, [restaurantId, userRecord, pathname, router]);

  useEffect(() => {
    fetch('/api/alert-settings', { credentials: 'include' })
      .then(r => r.ok ? r.json() : null)
      .then(d => { if (d?.settings) alertSettingsRef.current = d.settings; })
      .catch(() => {});
  }, []);

  useEffect(() => {
    if (typeof window === 'undefined') return;
    if (!('serviceWorker' in navigator)) return;
    navigator.serviceWorker.register('/sw.js').catch(() => {});
  }, []);

  const confirmOrdersRef = useRef<(ids: string[]) => Promise<void>>(async () => {});
  const confirmBookingsRef = useRef<(ids: string[]) => Promise<void>>(async () => {});
  useEffect(() => {
    if (typeof window === 'undefined') return;
    if (!('serviceWorker' in navigator)) return;
    const onMessage = (event: MessageEvent) => {
      const data = event.data as
        | { type?: string; action?: string; alertId?: string; orderIds?: string[]; bookingIds?: string[] }
        | undefined;
      if (!data || data.type !== 'notification-action') return;
      if (data.action !== 'accept') return;
      const orderIds = Array.isArray(data.orderIds) ? data.orderIds.filter(Boolean) : [];
      const bookingIds = Array.isArray(data.bookingIds) ? data.bookingIds.filter(Boolean) : [];
      if (data.alertId) {
        setLiveAlerts(prev => prev.filter(a => a.id !== data.alertId));
      }
      if (orderIds.length > 0) {
        confirmOrdersRef.current(orderIds).catch(() => {});
      }
      if (bookingIds.length > 0) {
        confirmBookingsRef.current(bookingIds).catch(() => {});
      }
    };
    navigator.serviceWorker.addEventListener('message', onMessage);
    return () => navigator.serviceWorker.removeEventListener('message', onMessage);
  }, []);

  useEffect(() => {
    const unsubscribe = onAlert((type, newCount, prevCount) => {
      const s = alertSettingsRef.current;
      if (type === 'order' && !s.showOrders) return;
      if (type === 'booking' && !s.showBookings) return;

      const diff = newCount - prevCount;
      const now = new Date();
      const ts = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
      const id = `live-alert-${++alertIdCounter.current}`;

      const _t = tRef.current;
      if (type === 'order') {
        const tooMany = diff > MAX_BULK_ACCEPT;
        const titleText = `${diff} ${_t('alerts.newOrder', 'New Order')}${diff > 1 ? 's' : ''}`;
        const bodyText = `${diff} ${_t('alerts.pendingOrders', 'pending order(s) awaiting confirmation')}`;
        const acceptLabel = diff > 1
          ? _t('alerts.acceptAll', 'Accept All')
          : _t('alerts.accept', 'Accept');
        const alert: LiveAlert = {
          id,
          type: 'order',
          title: titleText,
          subtitle: _t('alerts.justPlaced', 'Just placed'),
          details: [bodyText],
          timestamp: ts,
          countdown: s.autoDismiss,
          acceptOrderIds: [],
          // Hide Accept until we've successfully fetched the IDs to mutate.
          // For a too-large diff we never enable it (user opens the order page).
          acceptDisabled: true,
          acceptLabel,
        };
        setLiveAlerts(prev => [...prev, alert]);

        // Show an OS-level notification (with an Accept action when supported).
        // Initial render has no order IDs yet — once they're fetched below,
        // we re-show under the same tag to populate the action button.
        if (s.browserNotifications) {
          showOrderNotification(id, titleText, bodyText, acceptLabel, []);
        }

        // Fetch the most recent pending order IDs so Accept can confirm them.
        if (!tooMany) {
          listOrders({ status: 'pending', limit: String(diff) })
            .then(res => {
              const ids = (res?.orders ?? []).map(o => o.id).filter(Boolean) as string[];
              if (ids.length === 0) return;
              setLiveAlerts(prev => prev.map(a =>
                a.id === id ? { ...a, acceptOrderIds: ids, acceptDisabled: false } : a
              ));
              if (alertSettingsRef.current.browserNotifications) {
                showOrderNotification(id, titleText, bodyText, acceptLabel, ids);
              }
            })
            .catch(() => {
              // Leave acceptDisabled=true so the button stays hidden.
            });
        }
      } else {
        const titleText = `${diff} ${_t('alerts.newBooking', 'New Booking')}${diff > 1 ? 's' : ''}`;
        const bodyText = `${diff} ${_t('alerts.newReservations', 'new reservation(s) to review')}`;
        const acceptLabel = _t('alerts.confirm', 'Confirm');
        const alert: LiveAlert = {
          id,
          type: 'booking',
          title: titleText,
          subtitle: _t('alerts.justReceived', 'Just received'),
          details: [bodyText],
          timestamp: ts,
          countdown: s.autoDismiss,
          acceptBookingIds: [],
          // Hide Confirm until we've fetched a clear default booking ID.
          acceptDisabled: true,
          acceptLabel,
        };
        setLiveAlerts(prev => [...prev, alert]);

        // Show an OS-level notification (with Confirm action when there's a clear default).
        if (s.browserNotifications) {
          showBookingNotification(id, titleText, bodyText, acceptLabel, []);
        }

        // Only enable Confirm when there's exactly one new pending booking — i.e. a clear default.
        if (diff === 1) {
          listBookings({ status: 'pending', limit: '1', sortField: 'created_at', sortDir: 'desc' })
            .then(res => {
              const ids = (res?.bookings ?? []).map(b => b.id).filter(Boolean) as string[];
              if (ids.length === 0) return;
              setLiveAlerts(prev => prev.map(a =>
                a.id === id ? { ...a, acceptBookingIds: ids, acceptDisabled: false } : a
              ));
              if (alertSettingsRef.current.browserNotifications) {
                showBookingNotification(id, titleText, bodyText, acceptLabel, ids);
              }
            })
            .catch(() => {
              // Leave acceptDisabled=true so the button stays hidden.
            });
        }
      }
    });
    return unsubscribe;
  }, [onAlert]);

  useEffect(() => {
    if (!restaurantId) return;
    let aborted = false;
    const controller = new AbortController();
    const connect = () => {
      fetch('/api/alerts/escalations', { signal: controller.signal, headers: { 'Accept': 'text/event-stream' } }).then(async res => {
        if (!res.body || aborted) return;
        const reader = res.body.getReader();
        const decoder = new TextDecoder();
        let buffer = '';
        while (!aborted) {
          const { done, value } = await reader.read();
          if (done) break;
          buffer += decoder.decode(value, { stream: true });
          const lines = buffer.split('\n');
          buffer = lines.pop() || '';
          for (const line of lines) {
            if (!line.startsWith('data: ')) continue;
            try {
              const data = JSON.parse(line.slice(6));
              const s = alertSettingsRef.current;
              if (!s.showAiEscalations) continue;
              const ts = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
              const id = `escalation-${++alertIdCounter.current}`;
              const _t2 = tRef.current;
              setLiveAlerts(prev => [...prev, {
                id,
                type: 'escalation',
                title: _t2('alerts.aiEscalation', 'AI Escalation'),
                subtitle: data.reason || _t2('alerts.needsHumanAttention', 'Needs human attention'),
                details: [data.customerName ? `${_t2('alerts.customer', 'Customer')}: ${data.customerName}` : _t2('alerts.conversationRequiresIntervention', 'A conversation requires human intervention')],
                timestamp: ts,
                countdown: s.autoDismiss,
                acceptDisabled: true,
              }]);
            } catch {}
          }
        }
        if (!aborted) setTimeout(connect, 5000);
      }).catch(() => {
        if (!aborted) setTimeout(connect, 5000);
      });
    };
    connect();
    return () => { aborted = true; controller.abort(); };
  }, [restaurantId]);

  const handleDismissAlert = useCallback((id: string) => {
    setLiveAlerts(prev => prev.filter(a => a.id !== id));
  }, []);

  const confirmOrders = useCallback(async (ids: string[]) => {
    if (!ids || ids.length === 0) return;
    const _t = tRef.current;
    const results = await Promise.allSettled(
      ids.map(orderId => updateOrder(orderId, { status: 'confirmed' }))
    );
    const ok = results.filter(r => r.status === 'fulfilled').length;
    const fail = results.length - ok;

    if (fail === 0) {
      toast.success(
        ok === 1
          ? _t('alerts.orderConfirmed', 'Order confirmed')
          : _t('alerts.ordersConfirmed', `${ok} orders confirmed`, { count: ok })
      );
    } else if (ok === 0) {
      toast.error(_t('alerts.confirmFailed', 'Failed to confirm orders'));
    } else {
      toast.warning(
        _t(
          'alerts.partialConfirm',
          `Confirmed ${ok} of ${ok + fail} orders`,
          { ok, total: ok + fail }
        )
      );
    }
    refreshLiveCounts().catch(() => {});
  }, [refreshLiveCounts]);

  useEffect(() => { confirmOrdersRef.current = confirmOrders; }, [confirmOrders]);

  const confirmBookings = useCallback(async (ids: string[]) => {
    if (!ids || ids.length === 0) return;
    const _t = tRef.current;
    const results = await Promise.allSettled(
      ids.map(bookingId => updateBooking(bookingId, { status: 'confirmed' }))
    );
    const ok = results.filter(r => r.status === 'fulfilled').length;
    const fail = results.length - ok;

    if (fail === 0) {
      toast.success(
        ok === 1
          ? _t('alerts.bookingConfirmed', 'Booking confirmed')
          : _t('alerts.bookingsConfirmed', `${ok} bookings confirmed`, { count: ok })
      );
    } else if (ok === 0) {
      toast.error(_t('alerts.confirmBookingFailed', 'Failed to confirm bookings'));
    } else {
      toast.warning(
        _t(
          'alerts.partialBookingConfirm',
          `Confirmed ${ok} of ${ok + fail} bookings`,
          { ok, total: ok + fail }
        )
      );
    }
    refreshLiveCounts().catch(() => {});
  }, [refreshLiveCounts]);

  useEffect(() => { confirmBookingsRef.current = confirmBookings; }, [confirmBookings]);

  const handleAcceptAlert = useCallback(async (id: string) => {
    const alert = liveAlertsRef.current.find(a => a.id === id);
    setLiveAlerts(prev => prev.filter(a => a.id !== id));
    if (!alert) return;
    if (alert.type === 'order') {
      await confirmOrders(alert.acceptOrderIds ?? []);
    } else if (alert.type === 'booking') {
      await confirmBookings(alert.acceptBookingIds ?? []);
    }
  }, [confirmOrders, confirmBookings]);

  const handleExitImpersonation = useCallback(async () => {
    setExitingImpersonation(true);
    try {
      const res = await fetch('/api/admin/support/impersonate/exit', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ restaurantId: impersonatingRestaurantId }),
      });
      if (!res.ok) throw new Error('Exit failed');
      const data = await res.json() as { restaurantId?: string };
      const targetId = data.restaurantId ?? impersonatingRestaurantId;
      setImpersonatingName(null);
      setImpersonatingRestaurantId(null);
      router.push(targetId ? `/admin/restaurants/${targetId}` : '/admin/restaurants');
    } catch {
      toast.error(tRef.current('appLayout.exitImpersonationFailed', 'Failed to exit impersonation'));
      setExitingImpersonation(false);
    }
  }, [impersonatingRestaurantId, router]);

  const isAdmin = userRecord?.role === 'superadmin' || userRecord?.role === 'support';
  const isAdminPath = pathname?.startsWith('/admin');
  const effectiveAdminMode = isAdminPath || isAdminMode;
  const { isRTL, t } = useLanguage();
  const tRef = useRef(t);
  useEffect(() => { tRef.current = t; }, [t]);

  return (
    <div className="flex h-screen overflow-hidden" style={{ backgroundColor: 'hsl(var(--background))' }}>
      {mobileOpen && (
        <div
          className="fixed inset-0 bg-black/40 z-30 lg:hidden"
          onClick={() => setMobileOpen(false)}
        />
      )}

      <div className="hidden lg:flex shrink-0 sidebar-left" style={{ transition: 'width 300ms ease', width: isCollapsed ? '64px' : '240px' }}>
        <Sidebar
          isCollapsed={isCollapsed}
          onToggle={() => setIsCollapsed(!isCollapsed)}
          isAdminMode={effectiveAdminMode}
          onToggleAdminMode={isAdmin ? () => setIsAdminMode(!isAdminMode) : undefined}
          impersonatingName={impersonatingName ?? undefined}
          onExitImpersonation={impersonatingName ? handleExitImpersonation : undefined}
          exitingImpersonation={exitingImpersonation}
        />
      </div>

      <div
        className={`fixed inset-y-0 z-40 lg:hidden sidebar-left transition-transform duration-300 ${isRTL ? 'right-0' : 'left-0'} ${mobileOpen ? 'translate-x-0' : isRTL ? 'translate-x-full' : '-translate-x-full'}`}
        style={{ width: '240px' }}
      >
        <Sidebar
          isCollapsed={false}
          onToggle={() => setMobileOpen(false)}
          isAdminMode={effectiveAdminMode}
          onToggleAdminMode={isAdmin ? () => setIsAdminMode(!isAdminMode) : undefined}
          impersonatingName={impersonatingName ?? undefined}
          onExitImpersonation={impersonatingName ? handleExitImpersonation : undefined}
          exitingImpersonation={exitingImpersonation}
        />
      </div>

      <div className="flex flex-col flex-1 min-w-0 overflow-hidden">
        <Topbar
          onMobileMenuToggle={() => setMobileOpen(true)}
          pageTitle={pageTitle}
          pageSubtitle={pageSubtitle}
          isAdminMode={effectiveAdminMode}
        />
        <DemoReadOnlyBanner />
        <main className="flex-1 overflow-y-auto scrollbar-thin">
          <div className="max-w-screen-2xl mx-auto px-4 lg:px-6 xl:px-8 2xl:px-10 py-6">
            {children}
          </div>
        </main>
      </div>

      {liveAlerts.length > 0 && (
        <AlertOverlay
          alerts={liveAlerts}
          onDismiss={handleDismissAlert}
          onAccept={handleAcceptAlert}
          soundEnabled={alertSettingsRef.current.soundEnabled}
          volume={alertSettingsRef.current.volume}
        />
      )}
    </div>
  );
}

interface AppLayoutInitialBranding {
  site_title: string;
  site_primary_color: string;
  site_logo_url: string;
  site_favicon_url: string;
  site_font_family: string;
  site_tagline: string;
  site_support_email: string;
  site_marketing_url: string;
}

export default function AppLayout({
  children,
  initialBranding,
  initialBrandingVersion,
}: {
  children: React.ReactNode;
  initialBranding?: AppLayoutInitialBranding;
  initialBrandingVersion?: string;
}) {
  return (
    <BrandingProvider initialBranding={initialBranding} initialVersion={initialBrandingVersion}>
      <PlanFeaturesProvider>
        <LiveCountsProvider>
          <PageHeaderProvider>
            <AppLayoutInner>{children}</AppLayoutInner>
          </PageHeaderProvider>
        </LiveCountsProvider>
      </PlanFeaturesProvider>
    </BrandingProvider>
  );
}
