import { useEffect, useMemo, useState } from 'react';

import { triggerLocalAiAssetBootstrap } from '../../../utils/localAiBootstrap';
import {
  formatBytes,
  formatEta,
  progressFromDownloads,
  progressFromStatus,
} from '../../../utils/localAiHelpers';
import {
  type ApplyPresetResult,
  type LlmBackend,
  type LocalAiDownloadsProgress,
  type LocalAiStatus,
  memoryTreeGetLlm,
  memoryTreeSetLlm,
  openhumanGetConfig,
  openhumanLocalAiApplyPreset,
  openhumanLocalAiDownload,
  openhumanLocalAiDownloadAllAssets,
  openhumanLocalAiDownloadsProgress,
  openhumanLocalAiPresets,
  openhumanLocalAiStatus,
  openhumanUpdateLocalAiSettings,
  type PresetsResponse,
} from '../../../utils/tauriCommands';
import SettingsHeader from '../components/SettingsHeader';
import { useSettingsNavigation } from '../hooks/useSettingsNavigation';
import DeviceCapabilitySection from './local-model/DeviceCapabilitySection';

const formatRamGb = (bytes: number): string => {
  const gb = bytes / (1024 * 1024 * 1024);
  return gb >= 10 ? `${Math.round(gb)} GB` : `${gb.toFixed(1)} GB`;
};

const LocalModelPanel = () => {
  const { navigateBack, navigateToSettings, breadcrumbs } = useSettingsNavigation();
  const [status, setStatus] = useState<LocalAiStatus | null>(null);
  const [downloads, setDownloads] = useState<LocalAiDownloadsProgress | null>(null);
  const [statusError, setStatusError] = useState<string>('');
  const [isTriggeringDownload, setIsTriggeringDownload] = useState(false);
  const [bootstrapMessage, setBootstrapMessage] = useState<string>('');

  const [presetsData, setPresetsData] = useState<PresetsResponse | null>(null);
  const [presetsLoading, setPresetsLoading] = useState(true);
  const [presetError, setPresetError] = useState('');
  const [presetSuccess, setPresetSuccess] = useState<ApplyPresetResult | null>(null);

  const [usageFlags, setUsageFlags] = useState<{
    runtime_enabled: boolean;
    usage_embeddings: boolean;
    usage_heartbeat: boolean;
    usage_learning_reflection: boolean;
    usage_subconscious: boolean;
  }>({
    runtime_enabled: false,
    usage_embeddings: false,
    usage_heartbeat: false,
    usage_learning_reflection: false,
    usage_subconscious: false,
  });
  const [usageError, setUsageError] = useState('');
  const [usageSaving, setUsageSaving] = useState(false);

  // Memory summarizer backend lives in `memory_tree.llm_backend`, which is
  // outside the `local_ai.usage.*` surface — so it has its own RPC pair
  // (`memoryTreeGetLlm` / `memoryTreeSetLlm`). This is now the only UI
  // surface for the cloud/local toggle (the Intelligence Memory tab's
  // BackendChooser was removed in this PR to eliminate the duplicate
  // control surface). The tab's local-only Ollama model picker reads
  // the same field at mount to decide visibility.
  const [summarizerBackend, setSummarizerBackend] = useState<LlmBackend>('cloud');
  const [summarizerSaving, setSummarizerSaving] = useState(false);

  const progress = useMemo(() => {
    const downloadProgress = progressFromDownloads(downloads);
    if (downloadProgress != null) return downloadProgress;
    return progressFromStatus(status);
  }, [downloads, status]);
  const currentState = downloads?.state ?? status?.state;
  const runtimeEnabled = usageFlags.runtime_enabled;
  const isInstalling = currentState === 'installing';
  const isIndeterminateDownload =
    isInstalling ||
    (currentState === 'downloading' &&
      typeof downloads?.progress !== 'number' &&
      typeof status?.download_progress !== 'number');
  const downloadedBytes = downloads?.downloaded_bytes ?? status?.downloaded_bytes;
  const totalBytes = downloads?.total_bytes ?? status?.total_bytes;
  const speedBps = downloads?.speed_bps ?? status?.download_speed_bps;
  const etaSeconds = downloads?.eta_seconds ?? status?.eta_seconds;

  const loadStatus = async () => {
    try {
      const [statusResponse, downloadsResponse] = await Promise.all([
        openhumanLocalAiStatus(),
        openhumanLocalAiDownloadsProgress(),
      ]);
      setStatus(statusResponse.result);
      setDownloads(downloadsResponse.result);
      setStatusError('');
    } catch (err) {
      const message = err instanceof Error ? err.message : 'Failed to read local model status';
      setStatusError(message);
      setStatus(null);
      setDownloads(null);
    }
  };

  const loadPresets = async () => {
    setPresetsLoading(true);
    try {
      const data = await openhumanLocalAiPresets();
      setPresetsData(data);
      setPresetError('');
    } catch (err) {
      const msg = err instanceof Error ? err.message : 'Failed to load presets';
      setPresetError(msg);
    } finally {
      setPresetsLoading(false);
    }
  };

  const loadUsage = async () => {
    try {
      const snap = await openhumanGetConfig();
      const localAi = (snap.result?.config?.local_ai ?? {}) as Record<string, unknown>;
      const usage = (localAi.usage ?? {}) as Record<string, unknown>;
      setUsageFlags({
        runtime_enabled: Boolean(localAi.runtime_enabled),
        usage_embeddings: Boolean(usage.embeddings),
        usage_heartbeat: Boolean(usage.heartbeat),
        usage_learning_reflection: Boolean(usage.learning_reflection),
        usage_subconscious: Boolean(usage.subconscious),
      });
      setUsageError('');
    } catch (err) {
      const msg = err instanceof Error ? err.message : 'Failed to load local AI flags';
      setUsageError(msg);
    }
  };

  const updateUsage = async (patch: Partial<typeof usageFlags>) => {
    const next = { ...usageFlags, ...patch };
    setUsageFlags(next);
    setUsageSaving(true);
    setUsageError('');
    try {
      await openhumanUpdateLocalAiSettings(patch);
    } catch (err) {
      const msg = err instanceof Error ? err.message : 'Failed to save local AI flags';
      setUsageError(msg);
      void loadUsage();
    } finally {
      setUsageSaving(false);
    }
  };

  const loadSummarizerBackend = async () => {
    try {
      const resp = await memoryTreeGetLlm();
      if (resp?.current === 'local' || resp?.current === 'cloud') {
        setSummarizerBackend(resp.current);
      }
    } catch (err) {
      // Non-fatal — the row stays at its default (cloud) and the user
      // can still flip it; the next save attempt will surface the error.
      console.warn('[local-model-panel] memoryTreeGetLlm failed', err);
    }
  };

  const updateSummarizerBackend = async (next: LlmBackend) => {
    // Belt-and-braces: the checkbox is already `disabled` when the
    // master runtime is off or a save is in flight, but the rendered
    // disabled attribute only stops real-browser click events — JSDOM
    // / fireEvent ignore it. Mirror the gate here so programmatic
    // dispatch can't sneak past the master switch either.
    if (!usageFlags.runtime_enabled || summarizerSaving) return;
    const prev = summarizerBackend;
    setSummarizerBackend(next);
    setSummarizerSaving(true);
    setUsageError('');
    try {
      await memoryTreeSetLlm({ backend: next });
    } catch (err) {
      // Roll back the optimistic toggle and surface the error.
      setSummarizerBackend(prev);
      const msg = err instanceof Error ? err.message : 'Failed to save memory summarizer backend';
      setUsageError(msg);
    } finally {
      setSummarizerSaving(false);
    }
  };

  useEffect(() => {
    const initialLoad = window.setTimeout(() => {
      void loadStatus();
      void loadPresets();
      void loadUsage();
      void loadSummarizerBackend();
    }, 0);
    const timer = window.setInterval(() => {
      void loadStatus();
    }, 1500);
    return () => {
      window.clearTimeout(initialLoad);
      window.clearInterval(timer);
    };
  }, []);

  const triggerDownload = async (force: boolean) => {
    if (!runtimeEnabled) return;
    setIsTriggeringDownload(true);
    setStatusError('');
    setBootstrapMessage('');
    try {
      await openhumanLocalAiDownload(force);
      await openhumanLocalAiDownloadAllAssets(force);
      const freshStatus = await openhumanLocalAiStatus();
      setStatus(freshStatus.result);
      if (freshStatus.result?.state === 'ready') {
        setBootstrapMessage(force ? 'Re-bootstrap complete' : 'Models verified');
      }
      setTimeout(() => setBootstrapMessage(''), 3000);
    } catch (err) {
      const message =
        err instanceof Error ? err.message : 'Failed to trigger local model bootstrap';
      setStatusError(message);
    } finally {
      setIsTriggeringDownload(false);
    }
  };

  /**
   * Install-Ollama entry point used by the locked tier picker and the
   * runtime-status install CTA.
   *
   * Three preconditions need to be flipped before `download_all_models`
   * will actually run on the core side:
   *   - `runtime_enabled = true`   (cloud-fallback override off)
   *   - `selected_tier`            (anything other than "disabled")
   *   - `opt_in_confirmed = true`  (so bootstrap doesn't hard-override
   *                                 to disabled in `config_with_recommended_tier_if_unselected`)
   *
   * `apply_preset(<real tier>)` sets all three in one save. We call it
   * **unconditionally** here rather than going through
   * `ensureRecommendedLocalAiPresetIfNeeded`, because that helper
   * short-circuits when `selected_tier` is already set — which is exactly
   * the case for users who previously picked "Disabled (cloud fallback)"
   * and now want to switch on local AI. Without the explicit apply,
   * runtime_enabled stays false, `download_all_models` returns
   * `local ai is disabled`, the task marks the service degraded, and the
   * UI silently bounces back to idle.
   */
  const triggerInstallWithRecommendedTier = async () => {
    setIsTriggeringDownload(true);
    setStatusError('');
    setBootstrapMessage('');
    try {
      const presetsResult = presetsData ?? (await openhumanLocalAiPresets());
      const tier = presetsResult.recommended_tier || 'ram_2_4gb';
      if (tier === 'disabled') {
        throw new Error('Cannot install Ollama for the "disabled" tier — pick a local tier first.');
      }
      await openhumanLocalAiApplyPreset(tier);
      await triggerLocalAiAssetBootstrap(true);
      await loadPresets();
      const freshStatus = await openhumanLocalAiStatus();
      setStatus(freshStatus.result);
      setBootstrapMessage('Install started — Ollama and models are downloading');
      setTimeout(() => setBootstrapMessage(''), 4000);
    } catch (err) {
      const message = err instanceof Error ? err.message : 'Failed to start Ollama install';
      setStatusError(message);
    } finally {
      setIsTriggeringDownload(false);
    }
  };

  return (
    <div>
      <SettingsHeader
        title="Local AI Model"
        showBackButton={true}
        onBack={navigateBack}
        breadcrumbs={breadcrumbs}
      />

      <div className="p-4 space-y-4">
        <DeviceCapabilitySection
          presetsData={presetsData}
          presetsLoading={presetsLoading}
          presetError={presetError}
          presetSuccess={presetSuccess}
          formatRamGb={formatRamGb}
          ollamaAvailable={downloads?.ollama_available ?? true}
          onTriggerOllamaInstall={() => void triggerInstallWithRecommendedTier()}
          isTriggeringInstall={isTriggeringDownload}
          installState={status?.state}
          installWarning={status?.warning}
          installError={status?.error_detail}
          onPresetApplied={result => {
            setPresetSuccess(result);
            void loadPresets();
            void loadStatus();
          }}
        />

        {/*
          Simplified Model Status — only meaningful AFTER Ollama is on disk.
          Before that, every readout here ("idle"/"missing", Re-bootstrap
          button, refresh) is noise: there's no runtime to inspect and the
          right call-to-action is "Install Ollama" up top in the tier-picker
          banner. Hide the whole section to keep the UI progressive:
          1) Ollama missing            → only the install CTA (above)
          2) Ollama installing         → CTA flips to blue progress (above)
          3) Ollama installed onward   → this section appears with model state
        */}
        {(downloads?.ollama_available ?? true) && (
          <section className="bg-stone-50 rounded-lg border border-stone-200 p-4 space-y-3">
            <h3 className="text-sm font-semibold text-stone-900">Model Status</h3>

            <div className="text-sm text-stone-600">
              State:{' '}
              <span
                className={`font-medium ${
                  currentState === 'ready'
                    ? 'text-green-600'
                    : currentState === 'downloading' || currentState === 'installing'
                      ? 'text-primary-600'
                      : currentState === 'degraded'
                        ? 'text-amber-700'
                        : 'text-stone-700'
                }`}>
                {currentState ?? 'unknown'}
              </span>
            </div>

            {(currentState === 'downloading' || isInstalling) && (
              <div className="space-y-2">
                <div className="w-full h-2 rounded-full bg-stone-200 overflow-hidden">
                  {isIndeterminateDownload ? (
                    <div className="h-full bg-primary-500 animate-pulse rounded-full w-1/2" />
                  ) : (
                    <div
                      className="h-full bg-primary-500 rounded-full transition-all"
                      style={{ width: `${String(Math.min(progress ?? 0, 100))}%` }}
                    />
                  )}
                </div>
                <div className="flex justify-between text-xs text-stone-500">
                  <span>
                    {typeof downloadedBytes === 'number'
                      ? `${formatBytes(downloadedBytes)}${typeof totalBytes === 'number' ? ` / ${formatBytes(totalBytes)}` : ''}`
                      : ''}
                  </span>
                  <span>
                    {typeof speedBps === 'number' && speedBps > 0
                      ? `${formatBytes(speedBps)}/s`
                      : ''}
                    {etaSeconds ? ` · ${formatEta(etaSeconds)}` : ''}
                  </span>
                </div>
              </div>
            )}

            {bootstrapMessage && <div className="text-xs text-green-700">{bootstrapMessage}</div>}

            <div className="flex gap-2">
              <button
                type="button"
                onClick={() => void triggerDownload(false)}
                disabled={!runtimeEnabled || isTriggeringDownload}
                className="rounded-lg border border-primary-400 bg-primary-50 px-3 py-2 text-sm text-primary-700 disabled:opacity-50">
                {isTriggeringDownload ? 'Downloading…' : 'Download Models'}
              </button>
              <button
                type="button"
                onClick={() => void loadStatus()}
                className="rounded-lg border border-stone-300 bg-stone-100 px-3 py-2 text-sm text-stone-700">
                Refresh
              </button>
            </div>

            {statusError && (
              <div className="rounded-md border border-red-200 bg-red-50 p-3 text-xs text-red-600">
                {statusError}
              </div>
            )}
          </section>
        )}

        <section className="bg-stone-50 rounded-lg border border-stone-200 p-4 space-y-3">
          <div>
            <h3 className="text-sm font-semibold text-stone-900">Usage</h3>
            <p className="text-xs text-stone-500 mt-0.5">
              Choose which subsystems run on the local model. Anything off uses the cloud.
            </p>
          </div>

          <label className="flex items-start gap-3 cursor-pointer">
            <input
              type="checkbox"
              className="mt-0.5"
              checked={usageFlags.runtime_enabled}
              disabled={usageSaving}
              onChange={e => void updateUsage({ runtime_enabled: e.target.checked })}
            />
            <div>
              <div className="text-sm text-stone-900">Enable local AI runtime</div>
              <div className="text-xs text-stone-500">
                Master switch. Off by default — Ollama stays idle. When on, the tree summarizer,
                screen intelligence, and autocomplete always use the local model.
              </div>
            </div>
          </label>

          <div className={`space-y-2 pl-6 ${usageFlags.runtime_enabled ? '' : 'opacity-50'}`}>
            {/* Memory summarizer is special: it writes `memory_tree.llm_backend`,
                not `local_ai.usage.*`. This is now the sole UI surface for
                that field (the Intelligence Memory tab's BackendChooser was
                removed); the tab's Ollama model picker still reads it at
                mount to gate visibility. */}
            <label
              className="flex items-start gap-3 cursor-pointer"
              data-testid="local-ai-usage-memory-summarizer">
              <input
                type="checkbox"
                className="mt-0.5"
                checked={summarizerBackend === 'local'}
                disabled={!usageFlags.runtime_enabled || summarizerSaving}
                onChange={e => void updateSummarizerBackend(e.target.checked ? 'local' : 'cloud')}
              />
              <div>
                <div className="text-sm text-stone-900">Memory summarizer</div>
                <div className="text-xs text-stone-500">
                  Run memory-tree extract + summarise locally instead of in the cloud. The local
                  model used comes from the Model Tier preset above.
                </div>
              </div>
            </label>

            {(
              [
                {
                  key: 'usage_embeddings' as const,
                  label: 'Embeddings',
                  hint: 'Generate memory embeddings locally instead of in the cloud.',
                },
                {
                  key: 'usage_heartbeat' as const,
                  label: 'Heartbeat',
                  hint: 'Run heartbeat reasoning locally.',
                },
                {
                  key: 'usage_learning_reflection' as const,
                  label: 'Learning / reflection',
                  hint: 'Run learning and reflection passes locally.',
                },
                {
                  key: 'usage_subconscious' as const,
                  label: 'Subconscious',
                  hint: 'Run subconscious evaluation locally.',
                },
              ] as const
            ).map(({ key, label, hint }) => (
              <label key={key} className="flex items-start gap-3 cursor-pointer">
                <input
                  type="checkbox"
                  className="mt-0.5"
                  checked={usageFlags[key]}
                  disabled={!usageFlags.runtime_enabled || usageSaving}
                  onChange={e => void updateUsage({ [key]: e.target.checked })}
                />
                <div>
                  <div className="text-sm text-stone-900">{label}</div>
                  <div className="text-xs text-stone-500">{hint}</div>
                </div>
              </label>
            ))}
          </div>

          {usageError && (
            <div className="rounded-md border border-red-200 bg-red-50 p-3 text-xs text-red-600">
              {usageError}
            </div>
          )}
        </section>

        <button
          type="button"
          onClick={() => {
            if (runtimeEnabled) navigateToSettings('local-model-debug');
          }}
          disabled={!runtimeEnabled}
          className="flex items-center gap-1.5 text-xs text-stone-400 hover:text-stone-600 transition-colors disabled:opacity-50 disabled:hover:text-stone-400">
          Advanced settings
          <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
          </svg>
        </button>
      </div>
    </div>
  );
};

export default LocalModelPanel;
