import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocalStorageState } from '../../hooks/useLocalStorageState';
import {
  isNotificationSupported,
  getNotificationPermission,
  requestNotificationPermission,
  createNotification,
  buildNotificationOptions,
  DEFAULT_NOTIFY_EVENTS,
  type NotifyEventType,
  type NotifyEventSettings,
} from '../../lib/notifications';
import {
  isPushSupported,
  isStandalonePWA,
  isIOS,
  subscribePush,
  unsubscribePush,
  getCurrentPushSubscription,
} from '../../lib/push-subscribe';
import {
  fetchVapidPublicKey,
  listPushSubscriptions,
  postPushSubscription,
  deletePushSubscription as apiDeletePushSubscription,
  fetchNotificationPrefs,
  updateNotificationPrefs,
  migrateLocalStoragePrefs,
  postTestNotification,
  type PushSubscriptionPublic,
  type NotificationPrefsDTO,
} from '../../api';
import { HelpText } from './HelpText';

const EVENT_KEYS: NotifyEventType[] = ['running', 'succeeded', 'failed', 'waiting_human'];

type PushAvailability =
  | { kind: 'supported' }
  | { kind: 'needs-pwa-ios' }
  | { kind: 'unsupported' };

function evaluatePushAvailability(): PushAvailability {
  if (!isPushSupported()) {
    return { kind: 'unsupported' };
  }
  if (isIOS() && !isStandalonePWA()) {
    return { kind: 'needs-pwa-ios' };
  }
  return { kind: 'supported' };
}

export function NotificationsForm() {
  const { t } = useTranslation('settings');
  const supported = isNotificationSupported();
  const [permission, setPermission] = useState<NotificationPermission | 'unsupported'>(
    getNotificationPermission(),
  );
  // V1 localStorage (legacy fallback; server prefs override once loaded).
  const [v1Enabled, setV1Enabled] = useLocalStorageState<boolean>('notify.enabled', true);
  const [v1Events, setV1Events] = useLocalStorageState<NotifyEventSettings>(
    'notify.events',
    DEFAULT_NOTIFY_EVENTS,
  );

  // V2 server-side state — null until first fetch / not configured.
  const [serverPrefs, setServerPrefs] = useState<NotificationPrefsDTO | null>(null);
  const [subscriptions, setSubscriptions] = useState<PushSubscriptionPublic[]>([]);
  const [pushAvailable] = useState<PushAvailability>(() => evaluatePushAvailability());
  const [hasLocalSubscription, setHasLocalSubscription] = useState<boolean>(false);
  const [pushFatal, setPushFatal] = useState<string | null>(null);
  const [busy, setBusy] = useState<boolean>(false);

  // Effective prefs come from the server when available; otherwise fall back
  // to localStorage so V1-only deployments keep working unchanged.
  const enabled = serverPrefs?.enabled ?? v1Enabled;
  const events: NotifyEventSettings = serverPrefs
    ? serverPrefs.events
    : v1Events;
  const includeDetails = serverPrefs?.includeDetails ?? false;

  const setEnabled = useCallback(
    async (next: boolean) => {
      setV1Enabled(next);
      if (serverPrefs) {
        const updated = await updateNotificationPrefs({ enabled: next });
        setServerPrefs(updated);
      }
    },
    [serverPrefs, setV1Enabled],
  );

  const toggleEvent = useCallback(
    async (key: NotifyEventType) => {
      const nextValue = !events[key];
      setV1Events(prev => ({ ...prev, [key]: nextValue }));
      if (serverPrefs) {
        const updated = await updateNotificationPrefs({ events: { [key]: nextValue } });
        setServerPrefs(updated);
      }
    },
    [events, serverPrefs, setV1Events],
  );

  const setIncludeDetails = useCallback(
    async (next: boolean) => {
      if (!serverPrefs) return;
      const updated = await updateNotificationPrefs({ includeDetails: next });
      setServerPrefs(updated);
    },
    [serverPrefs],
  );

  // First-load: hydrate server prefs, migrate from localStorage if needed.
  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const prefs = await fetchNotificationPrefs();
        if (cancelled) return;
        if (!prefs.v1Migrated) {
          // One-shot import from localStorage. 409 means another tab beat us;
          // in that case just adopt the server state.
          const result = await migrateLocalStoragePrefs({
            enabled: v1Enabled,
            events: v1Events,
            includeDetails: false,
          });
          if (cancelled) return;
          if ('alreadyMigrated' in result) {
            setServerPrefs(prefs);
          } else {
            setServerPrefs(result.prefs);
          }
        } else {
          setServerPrefs(prefs);
        }
      } catch (err) {
        // /api/notifications/preferences not reachable → keep V1 fallback.
        // Logged for diagnostics; the UI remains usable.
        console.warn('[notifications] failed to load server prefs', err);
      }
    })();
    return () => { cancelled = true; };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Subscriptions list — re-fetch on mount and after subscribe/unsubscribe.
  const refreshSubscriptions = useCallback(async () => {
    try {
      const list = await listPushSubscriptions();
      setSubscriptions(list);
    } catch (err) {
      console.warn('[notifications] failed to load subscriptions', err);
    }
    try {
      const local = await getCurrentPushSubscription();
      setHasLocalSubscription(local !== null);
    } catch {
      setHasLocalSubscription(false);
    }
  }, []);

  useEffect(() => {
    if (pushAvailable.kind !== 'supported') return;
    refreshSubscriptions();
  }, [pushAvailable.kind, refreshSubscriptions]);

  useEffect(() => {
    const refresh = () => setPermission(getNotificationPermission());
    window.addEventListener('focus', refresh);
    return () => window.removeEventListener('focus', refresh);
  }, []);

  if (!supported) {
    return (
      <div className="space-y-4">
        <section>
          <h3 className="text-sm font-bold text-slate-900">{t('notifications.v1UnsupportedTitle')}</h3>
          <HelpText>{t('notifications.v1Unsupported')}</HelpText>
        </section>
      </div>
    );
  }

  const handleEnable = async () => {
    const result = await requestNotificationPermission();
    setPermission(result === 'unsupported' ? 'unsupported' : result);
    if (result === 'granted') void setEnabled(true);
    else void setEnabled(false);
  };

  const handleTestV1 = () => {
    const opts = buildNotificationOptions(
      { id: 0, title: t('notifications.testTitle'), pieceName: t('notifications.testBody') },
      'succeeded',
    );
    createNotification(opts, () => { /* no-op */ });
  };

  const handleSubscribe = async () => {
    if (pushAvailable.kind !== 'supported') return;
    setBusy(true);
    setPushFatal(null);
    try {
      const { publicKey } = await fetchVapidPublicKey();
      const dto = await subscribePush(publicKey);
      await postPushSubscription(dto);
      await refreshSubscriptions();
    } catch (err) {
      setPushFatal(err instanceof Error ? err.message : String(err));
    } finally {
      setBusy(false);
    }
  };

  const handleUnsubscribeLocal = async () => {
    setBusy(true);
    setPushFatal(null);
    try {
      const local = await getCurrentPushSubscription();
      // Match the server-side row by endpoint host (server returns only the host).
      const targetHost = local ? (() => {
        try { return new URL(local.endpoint).host; } catch { return null; }
      })() : null;
      const serverRow = subscriptions.find(s => targetHost && s.endpointHost === targetHost);
      await unsubscribePush();
      if (serverRow) await apiDeletePushSubscription(serverRow.id);
      await refreshSubscriptions();
    } catch (err) {
      setPushFatal(err instanceof Error ? err.message : String(err));
    } finally {
      setBusy(false);
    }
  };

  const handleDeleteRemote = async (id: string) => {
    setBusy(true);
    setPushFatal(null);
    try {
      await apiDeletePushSubscription(id);
      await refreshSubscriptions();
    } catch (err) {
      setPushFatal(err instanceof Error ? err.message : String(err));
    } finally {
      setBusy(false);
    }
  };

  const handleTestV2 = async () => {
    setBusy(true);
    setPushFatal(null);
    try {
      await postTestNotification();
    } catch (err) {
      setPushFatal(err instanceof Error ? err.message : String(err));
    } finally {
      setBusy(false);
    }
  };

  const v1StatusBadge = (() => {
    if (permission === 'granted' && enabled) return t('notifications.status.enabled');
    if (permission === 'granted' && !enabled) return t('notifications.status.paused');
    if (permission === 'denied') return t('notifications.status.denied');
    return t('notifications.status.notAllowed');
  })();

  return (
    <div className="space-y-6">
      {/* ── V1: foreground notifications ── */}
      <section>
        <h3 className="text-sm font-bold text-slate-900">{t('notifications.v1Title')}</h3>
        <p className="mt-1 text-[13px] text-slate-700">{t('notifications.statusLabel', { status: v1StatusBadge })}</p>

        {permission === 'default' && (
          <button
            onClick={handleEnable}
            className="mt-2 px-3 py-1.5 rounded bg-accent text-accent-fg text-[13px]"
          >
            {t('notifications.enableButton')}
          </button>
        )}

        {permission === 'denied' && (
          <HelpText>
            {t('notifications.deniedHelp')}
          </HelpText>
        )}

        {permission === 'granted' && (
          <label className="mt-2 flex items-center gap-2 text-[13px]">
            <input
              type="checkbox"
              checked={enabled}
              onChange={e => void setEnabled(e.target.checked)}
            />
            {t('notifications.masterToggle')}
          </label>
        )}

        {permission === 'granted' && (
          <button
            onClick={handleTestV1}
            disabled={!enabled}
            className="mt-2 px-3 py-1.5 rounded border border-slate-300 text-[13px]"
          >
            {t('notifications.testV1')}
          </button>
        )}
      </section>

      {/* ── V2: mobile / background notifications ── */}
      <section>
        <h3 className="text-sm font-bold text-slate-900">{t('notifications.v2Title')}</h3>

        {pushAvailable.kind === 'unsupported' && (
          <HelpText>{t('notifications.pushUnsupported')}</HelpText>
        )}

        {pushAvailable.kind === 'needs-pwa-ios' && (
          <HelpText>
            {t('notifications.iosPwaHelp')}
          </HelpText>
        )}

        {pushAvailable.kind === 'supported' && (
          <div className="mt-2 space-y-2">
            <p className="text-[13px] text-slate-700">
              {hasLocalSubscription ? t('notifications.deviceSubscribed') : t('notifications.deviceNotSubscribed')}
              {subscriptions.length > 0 && t('notifications.deviceCount', { count: subscriptions.length })}
            </p>
            <div className="flex gap-2">
              <button
                onClick={handleSubscribe}
                disabled={busy || hasLocalSubscription || !enabled}
                className="px-3 py-1.5 rounded bg-accent text-accent-fg text-[13px] disabled:opacity-50"
              >
                {t('notifications.subscribe')}
              </button>
              {hasLocalSubscription && (
                <button
                  onClick={handleUnsubscribeLocal}
                  disabled={busy}
                  className="px-3 py-1.5 rounded border border-slate-300 text-[13px]"
                >
                  {t('notifications.unsubscribe')}
                </button>
              )}
              <button
                onClick={handleTestV2}
                disabled={busy || subscriptions.length === 0}
                className="px-3 py-1.5 rounded border border-slate-300 text-[13px]"
              >
                {t('notifications.testV2')}
              </button>
            </div>

            {subscriptions.length > 0 && (
              <div className="mt-2 border border-slate-200 rounded">
                <p className="px-3 py-1 text-[12px] text-slate-600 border-b border-slate-200">
                  {t('notifications.deviceListTitle')}
                </p>
                {subscriptions.map(s => (
                  <div key={s.id} className="flex items-center justify-between px-3 py-2 text-[13px] border-b border-slate-100 last:border-b-0">
                    <div>
                      <div className="truncate max-w-md">{s.userAgent ?? t('notifications.unknownDevice')}</div>
                      <div className="text-[11px] text-slate-500">
                        {s.endpointHost} • {new Date(s.createdAt).toLocaleString('ja-JP')}
                        {s.failureCount > 0 && (
                          <span className="ml-2 text-red-600">{t('notifications.failures', { count: s.failureCount })}</span>
                        )}
                      </div>
                    </div>
                    <button
                      onClick={() => handleDeleteRemote(s.id)}
                      disabled={busy}
                      className="ml-2 px-2 py-1 text-[11px] text-red-700 dark:text-red-300 hover:bg-red-50 dark:hover:bg-red-500/15 rounded"
                    >
                      {t('notifications.removeDevice')}
                    </button>
                  </div>
                ))}
              </div>
            )}

            {serverPrefs && (
              <label className="mt-2 flex items-center gap-2 text-[13px]">
                <input
                  type="checkbox"
                  checked={includeDetails}
                  onChange={e => void setIncludeDetails(e.target.checked)}
                />
                {t('notifications.includeDetails')}
                <span className="text-[11px] text-slate-500">
                  {t('notifications.includeDetailsHint')}
                </span>
              </label>
            )}

            {pushFatal && (
              <p className="text-[12px] text-red-700 dark:text-red-300">{t('notifications.error', { msg: pushFatal })}</p>
            )}
          </div>
        )}
      </section>

      {/* ── events to notify (V1 + V2 shared) ── */}
      <section>
        <h3 className="text-sm font-bold text-slate-900">{t('notifications.eventsTitle')}</h3>
        <div className="mt-2 space-y-1">
          {EVENT_KEYS.map(key => (
            <label key={key} className="flex items-center gap-2 text-[13px]">
              <input
                type="checkbox"
                checked={events[key]}
                onChange={() => void toggleEvent(key)}
                disabled={!enabled}
              />
              {t(`notifications.events.${key}`)}
            </label>
          ))}
        </div>
      </section>

      <HelpText>
        {t('notifications.footer1')}<br />
        {t('notifications.footer2')}<br />
        {t('notifications.footer3')}
      </HelpText>
    </div>
  );
}
