import React, { useEffect, useMemo, useRef, useState } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';
import { api } from './api.js';
import { Topbar } from './components/Topbar.jsx';
import { Sidebar } from './components/Sidebar.jsx';
import { StatusPane } from './components/StatusPane.jsx';
import { OverviewPage } from './components/OverviewPage.jsx';
import { ClientOverviewPage } from './components/ClientOverviewPage.jsx';
import { ServersPage } from './components/ServersPage.jsx';
import { RoutingPage } from './components/RoutingPage.jsx';
import { LogsPage } from './components/LogsPage.jsx';
import { SettingsPage } from './components/SettingsPage.jsx';
import { ConfigViewer } from './components/ConfigViewer.jsx';
import { Toasts } from './components/Toasts.jsx';

const ROLLBACK_WINDOW_MS = 12_000;

function getInitialPage() {
  const hash = window.location.hash.replace('#/', '').replace('#', '');
  const valid = ['overview', 'servers', 'routing', 'logs', 'settings'];
  return valid.includes(hash) ? hash : 'overview';
}

function App() {
  const [page, setPage] = useState(getInitialPage());
  const [state, setState] = useState(null);
  const [subscriptionUrl, setSubscriptionUrl] = useState('');
  const [servers, setServers] = useState([]);
  const [customRules, setCustomRules] = useState([]);
  const [devicesConfig, setDevicesConfig] = useState({
    defaultTransparentMode: 'vpn',
    proxyDefaultMode: 'vpn',
    devices: [],
  });
  const [clientSettings, setClientSettings] = useState({
    homeBypassEnabled: false,
    sharedProxyEnabled: false,
  });
  const [selectedTag, setSelectedTag] = useState('');
  const [pendingTag, setPendingTag] = useState('');
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState('');
  const [rulesSaveStatus, setRulesSaveStatus] = useState('saved');
  const [configOpen, setConfigOpen] = useState(false);
  const [pings, setPings] = useState({});
  const [toasts, setToasts] = useState([]);
  const [applyStatus, setApplyStatus] = useState('idle'); // idle | applying | error
  const [rollbackOffer, setRollbackOffer] = useState(null);

  const rulesDirtyRef = useRef(false);
  const rulesSaveTimerRef = useRef(null);
  const rulesRevisionRef = useRef(0);
  const rollbackTimerRef = useRef(null);

  function pushToast(toast) {
    const id = `t-${Date.now()}-${Math.random()}`;
    setToasts((prev) => [...prev, { id, ...toast }]);
  }
  function dismissToast(id) {
    setToasts((prev) => prev.filter((t) => t.id !== id));
  }

  function navigate(p) {
    setPage(p);
    window.location.hash = `#/${p}`;
  }

  useEffect(() => {
    function onHash() { setPage(getInitialPage()); }
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);

  async function loadState() {
    const data = await api.state();
    setState(data);
    setServers(data.servers || []);
    if (!rulesDirtyRef.current) setCustomRules(data.customRules || []);
    setDevicesConfig(data.devicesConfig || {
      defaultTransparentMode: 'vpn',
      proxyDefaultMode: 'vpn',
      devices: data.devices || [],
    });
    setClientSettings(data.clientSettings || { homeBypassEnabled: false, sharedProxyEnabled: false });
    setSelectedTag((prev) => prev || data.selectedTag || '');
    setPendingTag((prev) => prev || data.selectedTag || '');
  }

  useEffect(() => {
    loadState().catch((err) => setError(err.message));
    const timer = setInterval(() => loadState().catch(() => {}), 5000);
    return () => clearInterval(timer);
  }, []);

  useEffect(() => {
    if (state?.mode === 'client' && page !== 'overview') {
      navigate('overview');
    }
  }, [state?.mode, page]);

  useEffect(() => () => {
    if (rulesSaveTimerRef.current) clearTimeout(rulesSaveTimerRef.current);
    if (rollbackTimerRef.current) clearTimeout(rollbackTimerRef.current);
  }, []);

  async function withBusy(label, fn, { quiet = false } = {}) {
    setBusy(true);
    setError('');
    try {
      const result = await fn();
      if (!quiet && label) pushToast({ kind: 'success', title: label });
      return result;
    } catch (err) {
      setError(err.message);
      pushToast({ kind: 'danger', title: 'Ошибка', message: err.message, duration: 6000 });
      throw err;
    } finally {
      setBusy(false);
    }
  }

  // === Subscription ===
  async function fetchSubscription() {
    return withBusy('Подписка обновлена', async () => {
      const data = await api.subscription.fetch(subscriptionUrl || state?.subscriptionHost || '');
      setServers(data.servers || []);
      if (!selectedTag && data.servers?.length) {
        setSelectedTag(data.servers[0].tag);
        setPendingTag(data.servers[0].tag);
      }
      await loadState();
    });
  }

  async function forgetSubscription() {
    if (!confirm('Удалить подписку и остановить sing-box?')) return;
    return withBusy('Подписка удалена', async () => {
      await api.subscription.forget();
      setSubscriptionUrl('');
      setServers([]);
      setSelectedTag('');
      setPendingTag('');
      await loadState();
    });
  }

  // === Apply with rollback offer ===
  async function applyServer(tag) {
    const target = tag || selectedTag;
    if (!target) return;
    const previous = state?.selectedTag;
    setApplyStatus('applying');
    try {
      await withBusy('Сервер применён', async () => {
        await api.apply(target);
        await loadState();
      });
      setApplyStatus('idle');

      if (previous && previous !== target) {
        setRollbackOffer({ from: target, to: previous, expiresAt: Date.now() + ROLLBACK_WINDOW_MS });
        if (rollbackTimerRef.current) clearTimeout(rollbackTimerRef.current);
        rollbackTimerRef.current = setTimeout(() => setRollbackOffer(null), ROLLBACK_WINDOW_MS);
      }
    } catch {
      setApplyStatus('error');
    }
  }

  async function rollback() {
    if (rollbackTimerRef.current) clearTimeout(rollbackTimerRef.current);
    setRollbackOffer(null);
    return withBusy('Откат выполнен', async () => {
      const data = await api.rollback();
      setSelectedTag(data.selectedTag);
      setPendingTag(data.selectedTag);
      await loadState();
    });
  }

  // === sing-box control ===
  async function stopSingbox() {
    if (!confirm('Остановить sing-box? Трафик через шлюз перестанет ходить.')) return;
    return withBusy('Остановлено', async () => { await api.singbox.stop(); await loadState(); });
  }
  async function restartSingbox() {
    return withBusy('Перезапущено', async () => { await api.singbox.restart(); await loadState(); });
  }
  async function clearConfig() {
    if (!confirm('Сбросить config sing-box и остановить процесс?')) return;
    return withBusy('Config сброшен', async () => {
      await api.singbox.clear();
      setSelectedTag('');
      setPendingTag('');
      await loadState();
    });
  }

  async function toggleBypass() {
    const next = !state?.bypassMode;
    return withBusy(
      next ? 'Обход правил включён — весь трафик напрямую' : 'Обход правил отключён',
      async () => {
        await api.bypass(next);
        await loadState();
      },
    );
  }

  async function flushDirectCache() {
    return withBusy('Bypass-кэш сброшен', async () => {
      await api.directCache.flush();
      await loadState();
    });
  }

  // === Devices ===
  async function saveDevicesConfig(nextConfig) {
    try {
      const data = await api.devices.save(nextConfig);
      setDevicesConfig({
        defaultTransparentMode: data.defaultTransparentMode || data.defaultMode || 'vpn',
        proxyDefaultMode: data.proxyDefaultMode || 'vpn',
        devices: data.devices || [],
      });
      setState((prev) => prev ? {
        ...prev,
        devicesUpdatedAt: data.devicesUpdatedAt,
        sourceBypassCidrs: data.sourceBypassCidrs,
      } : prev);
      if (data.sourceBypassResult && data.sourceBypassResult.success === false) {
        pushToast({
          kind: 'warning',
          title: 'Bypass сохранён, но не применён',
          message: data.sourceBypassResult.error,
          duration: 7000,
        });
      }
    } catch (err) {
      pushToast({ kind: 'danger', title: 'Не удалось сохранить устройства', message: err.message });
    }
  }

  function addDevice() {
    const nextConfig = {
      ...devicesConfig,
      devices: [
        ...devicesConfig.devices,
        { id: `dev-${Date.now()}`, name: 'Новое устройство', enabled: true, ip: '', mac: '', mode: 'direct', lastSeen: null },
      ],
    };
    setDevicesConfig(nextConfig);
    saveDevicesConfig(nextConfig);
  }

  function updateDevice(id, patch) {
    const nextConfig = {
      ...devicesConfig,
      devices: devicesConfig.devices.map((d) => (d.id === id ? { ...d, ...patch } : d)),
    };
    setDevicesConfig(nextConfig);
    saveDevicesConfig(nextConfig);
  }

  function removeDevice(id) {
    const nextConfig = {
      ...devicesConfig,
      devices: devicesConfig.devices.filter((d) => d.id !== id),
    };
    setDevicesConfig(nextConfig);
    saveDevicesConfig(nextConfig);
  }

  function updateDeviceDefaults(patch) {
    const nextConfig = { ...devicesConfig, ...patch };
    setDevicesConfig(nextConfig);
    saveDevicesConfig(nextConfig);
  }

  async function saveClientSettings(nextSettings) {
    return withBusy(null, async () => {
      const data = await api.clientSettings.save(nextSettings);
      setClientSettings(data.clientSettings || { homeBypassEnabled: false, sharedProxyEnabled: false });
      await loadState();
    }, { quiet: true });
  }

  async function checkSharedProxy(url) {
    return withBusy('Общий proxy подключён', async () => {
      const data = await api.clientSettings.checkSharedProxy(url);
      setClientSettings(data.clientSettings || { homeBypassEnabled: false, sharedProxyEnabled: false });
      await loadState();
    });
  }

  // === Rules CRUD ===
  function emptyRule() {
    return {
      id: `rule-${Date.now()}`,
      name: 'Новое правило',
      enabled: true,
      outbound: 'direct',
      domains: [], domainSuffixes: [], domainKeywords: [],
      ipCidrs: [], ports: [], networks: [],
    };
  }

  function queueRulesSave(nextRules) {
    rulesDirtyRef.current = true;
    const revision = rulesRevisionRef.current + 1;
    rulesRevisionRef.current = revision;
    setRulesSaveStatus('pending');

    if (rulesSaveTimerRef.current) clearTimeout(rulesSaveTimerRef.current);
    rulesSaveTimerRef.current = setTimeout(() => saveRules(nextRules, { silent: true, revision }), 700);
  }

  async function saveRules(nextRules = customRules, options = {}) {
    const { silent = false, revision = rulesRevisionRef.current + 1 } = options;
    setError('');
    setRulesSaveStatus('saving');
    try {
      const data = await api.rules.save(nextRules);
      if (rulesRevisionRef.current === revision) {
        rulesDirtyRef.current = false;
        setCustomRules(data.rules || []);
        setRulesSaveStatus('saved');
        await loadState();
        if (!silent) pushToast({ kind: 'success', title: 'Правила сохранены' });
      } else {
        setRulesSaveStatus('pending');
      }
    } catch (err) {
      setError(err.message);
      setRulesSaveStatus('error');
      pushToast({ kind: 'danger', title: 'Не удалось сохранить', message: err.message });
    }
  }

  function saveRulesNow() {
    if (rulesSaveTimerRef.current) clearTimeout(rulesSaveTimerRef.current);
    rulesDirtyRef.current = true;
    const revision = rulesRevisionRef.current + 1;
    rulesRevisionRef.current = revision;
    saveRules(customRules, { silent: false, revision });
  }

  function updateRule(id, patch) {
    setCustomRules((rules) => {
      const next = rules.map((r) => (r.id === id ? { ...r, ...patch } : r));
      queueRulesSave(next);
      return next;
    });
  }
  function addRule() {
    setCustomRules((rules) => {
      const next = [emptyRule(), ...rules];
      queueRulesSave(next);
      return next;
    });
  }
  function addRuleFromTemplate(tpl) {
    setCustomRules((rules) => {
      const next = [tpl, ...rules];
      queueRulesSave(next);
      return next;
    });
  }
  function removeRule(id) {
    setCustomRules((rules) => {
      const next = rules.filter((r) => r.id !== id);
      queueRulesSave(next);
      return next;
    });
  }
  function reorderRules(next) {
    setCustomRules(next);
    queueRulesSave(next);
  }

  // === Computed ===
  const status = useMemo(() => {
    if (applyStatus === 'applying') return 'applying';
    if (applyStatus === 'error') return 'error';
    if (state?.singboxRunning) return 'running';
    if (state?.configExists) return 'stopped';
    return 'no_config';
  }, [state, applyStatus]);

  const activeServer = useMemo(
    () => servers.find((s) => s.tag === state?.selectedTag) || null,
    [servers, state?.selectedTag],
  );
  const isClientMode = state?.mode === 'client';

  const dirtyRules = rulesSaveStatus === 'pending' || rulesSaveStatus === 'saving';
  const dirtyDevices = Boolean(
    state?.devicesUpdatedAt &&
    (!state?.rulesAppliedAt || state.devicesUpdatedAt > state.rulesAppliedAt),
  );
  const dirtyServer = pendingTag && pendingTag !== state?.selectedTag;
  const dirtyRouting = dirtyRules || dirtyDevices;
  const dirty = dirtyRouting || dirtyServer;

  const sidebarBadges = {
    routing: dirtyRouting ? { kind: 'warn', text: '●' } : null,
    servers: dirtyServer ? { kind: 'warn', text: '●' } : null,
    settings: !state?.hasSubscription ? { kind: 'danger', text: '!' } : null,
  };

  // === Render ===
  return (
    <div className="app">
      <Topbar
        state={state}
        status={status}
        activeServer={activeServer}
        dirty={dirty}
        onRestart={restartSingbox}
        onTryApply={rollback}
      />

      <div className={`app-body${isClientMode ? ' client-mode' : ''}`}>
        {!isClientMode && <Sidebar active={page} onChange={navigate} badges={sidebarBadges} mode={state?.mode} />}

        <main className="app-main">
          {(page === 'overview' || isClientMode) && (
            isClientMode ? (
              <ClientOverviewPage
                state={state}
                status={status}
                activeServer={activeServer}
                busy={busy}
                subscriptionUrl={subscriptionUrl}
                setSubscriptionUrl={setSubscriptionUrl}
                servers={servers}
                pendingTag={pendingTag}
                setPendingTag={setPendingTag}
                clientSettings={clientSettings}
                onSaveClientSettings={saveClientSettings}
                onCheckSharedProxy={checkSharedProxy}
                onFetchSubscription={fetchSubscription}
                onApply={applyServer}
                onRestart={restartSingbox}
                onStop={stopSingbox}
              />
            ) : (
              <OverviewPage
                state={state}
                status={status}
                busy={busy}
                onRestart={restartSingbox}
                onStop={stopSingbox}
                onShowConfig={() => setConfigOpen(true)}
                onNav={navigate}
                onBypassToggle={toggleBypass}
                onFlushDirectCache={flushDirectCache}
              />
            )
          )}
          {page === 'servers' && !isClientMode && (
            <ServersPage
              state={state}
              servers={servers}
              selectedTag={selectedTag}
              setSelectedTag={setSelectedTag}
              pendingTag={pendingTag}
              setPendingTag={setPendingTag}
              busy={busy}
              onApply={applyServer}
              onRollback={rollback}
              pings={pings}
              setPings={setPings}
              pushToast={pushToast}
            />
          )}
          {page === 'routing' && !isClientMode && (
            <RoutingPage
              rules={customRules}
              saveStatus={rulesSaveStatus}
              busy={busy}
              onAdd={addRule}
              onAddTemplate={addRuleFromTemplate}
              onUpdate={updateRule}
              onRemove={removeRule}
              onSaveNow={saveRulesNow}
              onReorder={reorderRules}
              devicesConfig={devicesConfig}
              onUpdateDeviceDefaults={updateDeviceDefaults}
              onAddDevice={addDevice}
              onUpdateDevice={updateDevice}
              onRemoveDevice={removeDevice}
            />
          )}
          {page === 'logs' && !isClientMode && <LogsPage devices={devicesConfig.devices} />}
          {page === 'settings' && !isClientMode && (
            <SettingsPage
              state={state}
              subscriptionUrl={subscriptionUrl}
              setSubscriptionUrl={setSubscriptionUrl}
              busy={busy}
              onFetchSubscription={fetchSubscription}
              onForgetSubscription={forgetSubscription}
              onShowConfig={() => setConfigOpen(true)}
              onClearConfig={clearConfig}
              pushToast={pushToast}
            />
          )}

          {/* Sticky bar — для routing/servers */}
          {(page === 'routing' && dirtyRouting) && (
            <div className="sticky-bar">
              <div className="flex">
                <span className={`dot ${rulesSaveStatus === 'error' ? 'danger' : 'warning'}`} />
                <strong>
                  {rulesSaveStatus === 'saving' && 'Сохраняем…'}
                  {rulesSaveStatus === 'pending' && 'Есть несохранённые изменения'}
                  {rulesSaveStatus === 'saved' && dirtyDevices && 'Изменения устройств сохранены'}
                  {rulesSaveStatus === 'error' && 'Ошибка сохранения'}
                </strong>
                <small className="muted">Конфиг sing-box нужно пересобрать и применить.</small>
              </div>
              <div className="btn-group">
                {rulesSaveStatus !== 'saved' && (
                  <button className="btn btn-secondary sm" onClick={saveRulesNow}>Сохранить сейчас</button>
                )}
                {state?.selectedTag && (
                  <button className="btn btn-primary sm" onClick={() => applyServer(state.selectedTag)} disabled={busy}>
                    Применить config
                  </button>
                )}
              </div>
            </div>
          )}

          {(page === 'servers' && dirtyServer) && (
            <div className="sticky-bar">
              <div className="flex">
                <span className="dot warning" />
                <strong>Сервер не применён</strong>
                <small className="muted">Выбран: {pendingTag}</small>
              </div>
              <div className="btn-group">
                <button className="btn btn-ghost sm" onClick={() => setPendingTag(state?.selectedTag || '')}>Отменить</button>
                <button className="btn btn-primary sm" onClick={() => applyServer(pendingTag)} disabled={busy}>
                  Применить
                </button>
              </div>
            </div>
          )}
        </main>

        {!isClientMode && (
          <StatusPane
            state={state}
            busy={busy}
            onStop={stopSingbox}
            onRestart={restartSingbox}
            onShowConfig={() => setConfigOpen(true)}
          />
        )}
      </div>

      <ConfigViewer open={configOpen} onClose={() => setConfigOpen(false)} />
      <Toasts items={toasts} onDismiss={dismissToast} />

      {rollbackOffer && (
        <div className="toasts">
          <div className="toast warning">
            <span className="dot warning" style={{ marginTop: 4 }} />
            <div className="body">
              <strong>Сервер применён</strong>
              <small>Можно откатиться к «{rollbackOffer.to}»</small>
              <button className="btn btn-link" onClick={rollback} style={{ padding: 0, marginTop: 4 }}>
                ↶ Откатить
              </button>
            </div>
            <button onClick={() => setRollbackOffer(null)}>×</button>
          </div>
        </div>
      )}
    </div>
  );
}

createRoot(document.getElementById('root')).render(<App />);
