import { useState, lazy, Suspense, type ReactNode } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { PageHeader } from '../components/ui/PageHeader';
import { Callout } from '../components/ui/Callout';
import { Disclosure } from '../components/ui/Disclosure';
import { Badge } from '../components/ui/Badge';
import { SkeletonCard, SkeletonTable } from '../components/ui/Skeleton';
import { useAuthStore } from '../stores/authStore';
import {
  getRegimeMonitor,
  getRegimeConfig,
  updateRegimeConfig,
  getRegimeFundamentals,
  updateRegimeFundamentals,
  refreshRegimeFundamentals,
  getEventStudy,
} from '../api/regime';

// Lazy so recharts (heavy) ships in its own chunk, loaded only on this tab.
const ScoreHistoryChart = lazy(() => import('../components/regime/ScoreHistoryChart'));
const RegimeQuadrant = lazy(() => import('../components/regime/RegimeQuadrant'));
import type {
  RegimeBand,
  RegimeSignal,
  RegimeConfig,
  RegimeFundamentals,
  EventStudyReport,
  EventStudyLeadStats,
  EventStudyPerEvent,
} from '../lib/types';

const BAND_STYLES: Record<RegimeBand, { text: string; bar: string; ring: string; label: string }> = {
  stable: { text: 'text-emerald-400', bar: 'bg-emerald-400', ring: 'border-emerald-400/30', label: 'Stable' },
  watch: { text: 'text-amber-400', bar: 'bg-amber-400', ring: 'border-amber-400/30', label: 'Watch' },
  elevated: { text: 'text-orange-400', bar: 'bg-orange-400', ring: 'border-orange-400/30', label: 'Elevated' },
  breaking: { text: 'text-red-400', bar: 'bg-red-400', ring: 'border-red-400/30', label: 'Breaking' },
};

function TrendChip({ label, delta }: { label: string; delta: number | null | undefined }) {
  if (delta == null) {
    return <span className="rounded-lg bg-white/[0.04] px-2.5 py-1 text-xs text-gray-500">{label}: n/a</span>;
  }
  const rising = delta > 0;
  const flat = delta === 0;
  // Higher index = worse, so a rising score is the warning direction.
  const color = flat ? 'text-gray-400' : rising ? 'text-red-400' : 'text-emerald-400';
  const arrow = flat ? '→' : rising ? '↑' : '↓';
  return (
    <span className="rounded-lg bg-white/[0.04] px-2.5 py-1 text-xs text-gray-400">
      {label}: <span className={`font-medium ${color}`}>{arrow} {delta > 0 ? '+' : ''}{delta}</span>
    </span>
  );
}

function ScoreGauge({
  label,
  score,
  band,
  trend,
  threshold,
  footnote,
  size = 'lg',
}: {
  label: string;
  score: number | null | undefined;
  band: RegimeBand | null | undefined;
  trend?: { delta_7?: number | null; delta_30?: number | null };
  threshold?: number;
  footnote?: ReactNode;
  size?: 'lg' | 'md';
}) {
  const naa = score == null;
  const style = BAND_STYLES[(band ?? 'stable') as RegimeBand];
  const s = score ?? 0;
  const clamp = (v: number) => Math.min(100, Math.max(0, v));
  const numCls = size === 'lg' ? 'text-6xl' : 'text-4xl';
  return (
    <div className={`glass border ${naa ? 'border-white/[0.06]' : style.ring} p-6`}>
      <div className="flex flex-wrap items-end justify-between gap-3">
        <div>
          <div className="text-[11px] uppercase tracking-wider text-gray-500">{label}</div>
          <div className="mt-1 flex items-baseline gap-2">
            <span className={`font-display font-bold ${numCls} ${naa ? 'text-gray-600' : style.text}`}>
              {naa ? '—' : Math.round(s)}
            </span>
            {!naa && <span className="text-sm text-gray-500">/ 100</span>}
          </div>
          {!naa && <p className={`mt-0.5 text-sm font-medium ${style.text}`}>{style.label}</p>}
        </div>
        {trend && (
          <div className="flex flex-wrap gap-2">
            <TrendChip label="7d" delta={trend.delta_7} />
            <TrendChip label="30d" delta={trend.delta_30} />
          </div>
        )}
      </div>

      {!naa && (
        <>
          {/* Band track with score (+ optional threshold) markers */}
          <div className="relative mt-5 h-2 w-full rounded-full bg-gradient-to-r from-emerald-500/30 via-amber-500/30 to-red-500/40">
            {threshold != null && (
              <div
                className="absolute -top-1 h-4 w-0.5 -translate-x-1/2 rounded bg-gray-300/80"
                style={{ left: `${clamp(threshold)}%` }}
                title={`Alert threshold ${threshold}`}
              />
            )}
            <div
              className={`absolute -top-1.5 h-5 w-5 -translate-x-1/2 rounded-full border-2 border-white/70 ${style.bar}`}
              style={{ left: `${clamp(s)}%` }}
            />
          </div>
          <div className="mt-1.5 flex justify-between text-[10px] uppercase tracking-wider text-gray-600">
            <span>0</span><span>30</span><span>60</span><span>80</span><span>100</span>
          </div>
        </>
      )}
      {footnote && <p className="mt-4 text-xs leading-relaxed text-gray-500">{footnote}</p>}
    </div>
  );
}

function Breakdown({ breakdown }: { breakdown: RegimeSignal[] }) {
  return (
    <div className="glass overflow-hidden">
      <table className="w-full text-sm">
        <thead>
          <tr className="border-b border-white/[0.06] text-left text-xs uppercase tracking-wider text-gray-500">
            <th className="px-4 py-3 font-medium">Signal</th>
            <th className="px-4 py-3 font-medium">Sub-score</th>
            <th className="px-4 py-3 text-right font-medium">Weight</th>
            <th className="px-4 py-3 text-right font-medium">Contribution</th>
          </tr>
        </thead>
        <tbody>
          {breakdown.map((s) => (
            <tr key={s.id} className="border-b border-white/[0.03] last:border-0">
              <td className="px-4 py-3">
                <span className="font-mono text-[10px] text-gray-600">{s.id}</span>{' '}
                <span className="text-gray-300">{s.label}</span>
              </td>
              <td className="px-4 py-3">
                {s.available && s.sub_score != null ? (
                  <div className="flex items-center gap-2">
                    <div className="h-1.5 w-24 overflow-hidden rounded-full bg-white/[0.06]">
                      <div className="h-full rounded-full bg-blue-400/70" style={{ width: `${s.sub_score}%` }} />
                    </div>
                    <span className="num text-gray-300">{s.sub_score}</span>
                  </div>
                ) : (
                  <span className="text-xs text-gray-600">n/a</span>
                )}
              </td>
              <td className="px-4 py-3 text-right num text-gray-400">{s.weight}</td>
              <td className="px-4 py-3 text-right num text-gray-300">
                {s.available ? s.contribution.toFixed(1) : '—'}
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function SliderRow({ label, value, onChange }: { label: string; value: number; onChange: (v: number) => void }) {
  return (
    <label className="flex items-center gap-3 text-xs text-gray-400">
      <span className="w-52 shrink-0">{label}</span>
      <input
        type="range"
        min={0}
        max={100}
        value={value}
        onChange={(e) => onChange(parseInt(e.target.value, 10))}
        className="h-2 flex-1 cursor-pointer appearance-none rounded-lg bg-gray-700 accent-blue-500"
      />
      <span className="w-8 text-right num text-gray-300">{value}</span>
    </label>
  );
}

function FundamentalsEditor({
  data,
  onSave,
  onRefresh,
  saving,
  refreshing,
}: {
  data: RegimeFundamentals;
  onSave: (body: { f1_score?: number; f3_score?: number; locked?: boolean }) => void;
  onRefresh: () => void;
  saving: boolean;
  refreshing: boolean;
}) {
  const [f1, setF1] = useState(Math.round(data.f1_score));
  const [f3, setF3] = useState(Math.round(data.f3_score));
  return (
    <div className="space-y-3">
      <div className="flex flex-wrap items-center gap-2 text-xs text-gray-500">
        <span>Source: {data.source}</span>
        {data.fetched_at && <span>· {new Date(data.fetched_at).toLocaleDateString()}</span>}
        {data.locked && <Badge label="locked" variant="manual" />}
      </div>
      {data.reasoning && <p className="text-xs leading-relaxed text-gray-400">{data.reasoning}</p>}
      <SliderRow label="F1 · Hyperscaler capex guidance" value={f1} onChange={setF1} />
      <SliderRow label="F3 · Good news, stock down" value={f3} onChange={setF3} />
      <div className="flex flex-wrap gap-2 pt-1">
        <button
          className="btn-primary px-3 py-1.5 text-sm disabled:opacity-50"
          disabled={saving}
          onClick={() => onSave({ f1_score: f1, f3_score: f3, locked: true })}
        >
          Save override
        </button>
        <button
          className="rounded-lg px-3 py-1.5 text-sm text-gray-400 hover:bg-white/[0.04] hover:text-gray-200 disabled:opacity-50"
          disabled={refreshing}
          onClick={onRefresh}
        >
          {refreshing ? 'Refreshing…' : 'Refresh via LLM'}
        </button>
        {data.locked && (
          <button
            className="rounded-lg px-3 py-1.5 text-sm text-gray-400 hover:bg-white/[0.04] hover:text-gray-200 disabled:opacity-50"
            disabled={saving}
            onClick={() => onSave({ locked: false })}
          >
            Unlock
          </button>
        )}
      </div>
    </div>
  );
}

function WeightsEditor({
  data,
  onSave,
  saving,
}: {
  data: RegimeConfig;
  onSave: (updates: Partial<RegimeConfig>) => void;
  saving: boolean;
}) {
  const [weights, setWeights] = useState<Record<string, number>>(() => ({ ...data.weights }));
  const [threshold, setThreshold] = useState<number>(data.alert_threshold);

  const setWeight = (key: string, value: string) => {
    const num = parseFloat(value);
    setWeights((prev) => ({ ...prev, [key]: isNaN(num) ? 0 : num }));
  };

  return (
    <div className="space-y-3">
      <div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
        {Object.keys(weights).map((key) => (
          <label key={key} className="flex items-center justify-between gap-2 text-xs text-gray-400">
            <span className="font-mono text-gray-500">{key}</span>
            <input
              type="number"
              min={0}
              value={weights[key]}
              onChange={(e) => setWeight(key, e.target.value)}
              className="w-16 rounded-md border border-white/[0.08] bg-white/[0.03] px-2 py-1 text-right num text-gray-200"
            />
          </label>
        ))}
      </div>
      <label className="flex items-center gap-2 text-xs text-gray-400">
        <span>Alert threshold</span>
        <input
          type="number"
          min={0}
          max={100}
          value={threshold}
          onChange={(e) => setThreshold(parseInt(e.target.value, 10) || 0)}
          className="w-20 rounded-md border border-white/[0.08] bg-white/[0.03] px-2 py-1 text-right num text-gray-200"
        />
      </label>
      <button
        className="btn-primary px-3 py-1.5 text-sm disabled:opacity-50"
        disabled={saving}
        onClick={() => onSave({ weights, alert_threshold: threshold })}
      >
        Save weights
      </button>
    </div>
  );
}

function Sparkline({ values, color = '#60a5fa', height = 28 }: { values: number[]; color?: string; height?: number }) {
  if (values.length < 2) return null;
  const min = Math.min(...values);
  const max = Math.max(...values);
  const range = max - min || 1;
  const w = 120;
  const pts = values
    .map((v, i) => `${(i / (values.length - 1)) * w},${height - ((v - min) / range) * height}`)
    .join(' ');
  return (
    <svg width={w} height={height}>
      <polyline points={pts} fill="none" stroke={color} strokeWidth={1.5} />
    </svg>
  );
}

function pctLabel(v: number | null): string {
  return v == null ? '—' : `${Math.round(v * 100)}%`;
}

function leadLabel(v: number | null): string {
  return v == null ? 'missed' : `${v}d`;
}

function bestPr(stats: EventStudyLeadStats) {
  const rows = stats.signal.rows.filter((r) => r.precision != null && r.recall != null && r.recall > 0);
  if (!rows.length) return null;
  return rows.reduce((a, b) => ((b.precision ?? 0) > (a.precision ?? 0) ? b : a));
}

function LeadStat({ label, stats, highlight }: { label: string; stats: EventStudyLeadStats; highlight?: boolean }) {
  const pr = bestPr(stats);
  return (
    <div className={`rounded-lg border px-3 py-2 ${highlight ? 'border-blue-400/30 bg-blue-400/[0.06]' : 'border-white/[0.06] bg-white/[0.02]'}`}>
      <div className="text-xs text-gray-500">{label}</div>
      <div className="mt-0.5 text-lg font-semibold text-gray-200">
        {stats.median_lead_days != null ? `${stats.median_lead_days}d lead` : 'no signal'}
      </div>
      <div className="text-[11px] text-gray-600">
        {stats.events_with_signal}/{stats.events_total} warned
        {stats.warn_threshold != null ? ` · warn ≥ ${Math.round(stats.warn_threshold)}` : ''}
      </div>
      {pr && (
        <div className="text-[11px] text-gray-600">
          best P {pctLabel(pr.precision)} · R {pctLabel(pr.recall)} @ {pr.threshold}
        </div>
      )}
    </div>
  );
}

function PerEventTable({ rows }: { rows: EventStudyPerEvent[] }) {
  return (
    <div className="overflow-x-auto rounded-lg border border-white/[0.06]">
      <table className="w-full text-xs">
        <thead>
          <tr className="border-b border-white/[0.06] text-left uppercase tracking-wider text-gray-500">
            <th className="px-3 py-2 font-medium">Drawdown</th>
            <th className="px-3 py-2 text-right font-medium">Depth</th>
            <th className="px-3 py-2 text-right font-medium">Breadth lead</th>
            <th className="px-3 py-2 text-right font-medium">Coincident lead</th>
          </tr>
        </thead>
        <tbody>
          {rows.map((e) => {
            const earlier = e.breadth_lead != null && (e.coincident_lead == null || e.breadth_lead > e.coincident_lead);
            return (
              <tr key={e.date} className="border-b border-white/[0.03] last:border-0">
                <td className="px-3 py-2 num text-gray-300">{e.date}</td>
                <td className="px-3 py-2 text-right num text-gray-400">{e.depth_pct}%</td>
                <td className={`px-3 py-2 text-right num ${earlier ? 'text-emerald-400' : 'text-gray-300'}`}>
                  {leadLabel(e.breadth_lead)}
                </td>
                <td className="px-3 py-2 text-right num text-gray-300">{leadLabel(e.coincident_lead)}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

function EventStudyBody({ report }: { report: EventStudyReport }) {
  const bd = report.indicators!.breadth_divergence;
  const cd = report.indicators!.coincident_price;
  const recent = report.recent_breadth ?? [];
  const breadthVals = recent.map((r) => r.breadth);
  const divVals = recent.map((r) => r.divergence ?? 0);
  const moreCoverage = bd.events_with_signal > cd.events_with_signal;
  return (
    <div className="space-y-4">
      <p className="text-xs text-gray-500">
        {report.events?.length ?? 0} drawdown events (≥{report.params?.event_threshold_pct}%) on{' '}
        {report.params?.benchmark} over ~5y. With so few events, coverage (how many it warned before) matters
        more than the median lead.
      </p>
      <div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
        <LeadStat label="Breadth divergence (leading candidate)" stats={bd} highlight={moreCoverage} />
        <LeadStat label="Coincident price composite (baseline)" stats={cd} />
      </div>
      <p className="text-xs text-gray-400">
        Breadth divergence warned before{' '}
        <span className="font-medium text-emerald-400">{bd.events_with_signal}/{bd.events_total}</span> drawdowns
        {bd.median_lead_days != null ? ` (median ${bd.median_lead_days}d lead)` : ''}; the coincident baseline only{' '}
        <span className="font-medium text-gray-300">{cd.events_with_signal}/{cd.events_total}</span>. The median-lead
        comparison is unreliable when coverage differs this much — see per-drawdown below.
      </p>
      {report.per_event && report.per_event.length > 0 && (
        <div className="space-y-1.5">
          <div className="text-[11px] uppercase tracking-wider text-gray-500">Per drawdown (same events, both indicators)</div>
          <PerEventTable rows={report.per_event} />
        </div>
      )}
      {recent.length > 1 && (
        <div className="flex flex-wrap items-end gap-6">
          <div>
            <div className="text-[11px] text-gray-500">Breadth (% &gt; 200d), last 90d</div>
            <Sparkline values={breadthVals} color="#34d399" />
            <div className="num text-xs text-gray-400">{breadthVals[breadthVals.length - 1]?.toFixed(0)}%</div>
          </div>
          <div>
            <div className="text-[11px] text-gray-500">Divergence (fragility), last 90d</div>
            <Sparkline values={divVals} color="#fb923c" />
            <div className="num text-xs text-gray-400">{divVals[divVals.length - 1]?.toFixed(0)}</div>
          </div>
        </div>
      )}
      <p className="text-[11px] leading-relaxed text-gray-600">
        Base rate {Math.round(bd.signal.base_rate * 100)}% · horizon {bd.signal.horizon_days}d. Few events in
        5y → noisy; treat lead time as an order of magnitude and don&apos;t overfit thresholds. Not yet wired
        into the live score.
      </p>
    </div>
  );
}

function EventStudyPanel() {
  const study = useQuery({ queryKey: ['regime', 'event-study'], queryFn: getEventStudy });
  return (
    <Disclosure summary="Early-warning study — measured lead time vs. drawdowns">
      {study.isLoading && <SkeletonCard className="h-24" />}
      {study.data === null && (
        <Callout variant="empty">Not run yet — trigger the “Event Study” job in Admin → Jobs.</Callout>
      )}
      {study.data && !study.data.available && (
        <Callout variant="warning">{study.data.reason ?? 'No data'}</Callout>
      )}
      {study.data && study.data.available && <EventStudyBody report={study.data} />}
    </Disclosure>
  );
}

function AdminControls() {
  const qc = useQueryClient();
  const fundamentals = useQuery({ queryKey: ['regime', 'fundamentals'], queryFn: getRegimeFundamentals });
  const config = useQuery({ queryKey: ['regime', 'config'], queryFn: getRegimeConfig });

  const invalidate = () => qc.invalidateQueries({ queryKey: ['regime'] });
  const refresh = useMutation({ mutationFn: refreshRegimeFundamentals, onSuccess: invalidate });
  const saveFund = useMutation({ mutationFn: updateRegimeFundamentals, onSuccess: invalidate });
  const saveConfig = useMutation({ mutationFn: updateRegimeConfig, onSuccess: invalidate });

  return (
    <div className="space-y-3">
      <Disclosure summary="Admin · Fundamentals (F1 / F3)">
        {fundamentals.isLoading && <SkeletonCard className="h-24" />}
        {fundamentals.data && (
          <FundamentalsEditor
            key={fundamentals.dataUpdatedAt}
            data={fundamentals.data}
            onSave={(body) => saveFund.mutate(body)}
            onRefresh={() => refresh.mutate()}
            saving={saveFund.isPending}
            refreshing={refresh.isPending}
          />
        )}
        {refresh.isError && (
          <Callout variant="error">Refresh failed: {(refresh.error as Error).message}</Callout>
        )}
      </Disclosure>

      <Disclosure summary="Admin · Weights & threshold">
        {config.isLoading && <SkeletonCard className="h-24" />}
        {config.data && (
          <WeightsEditor
            key={config.dataUpdatedAt}
            data={config.data}
            onSave={(updates) => saveConfig.mutate(updates)}
            saving={saveConfig.isPending}
          />
        )}
      </Disclosure>
    </div>
  );
}

export default function RegimePage() {
  const role = useAuthStore((s) => s.role);
  const isAdmin = role === 'admin';
  const monitor = useQuery({ queryKey: ['regime', 'monitor'], queryFn: getRegimeMonitor });

  return (
    <div className="space-y-6 animate-slide-up">
      <PageHeader
        title="Regime Monitor"
        subtitle="AI/Tech regime-change index — observational, feeds no trades"
      />

      {monitor.isLoading && (
        <>
          <SkeletonCard className="h-44" />
          <SkeletonTable rows={6} cols={4} />
        </>
      )}

      {monitor.isError && (
        <Callout variant="error" onRetry={() => monitor.refetch()}>
          Failed to load: {(monitor.error as Error).message}
        </Callout>
      )}

      {monitor.data && !monitor.data.available && (
        <Callout variant="empty">
          Not computed yet — run the “Regime Monitor” job from Admin → Jobs, or wait for the daily pipeline.
        </Callout>
      )}

      {monitor.data && monitor.data.available && (
        <>
          <div className="grid gap-4 lg:grid-cols-2">
            <ScoreGauge
              label="Regime index · coincident"
              score={monitor.data.total_score}
              band={monitor.data.band}
              trend={monitor.data.trend}
              threshold={monitor.data.alert_threshold}
              footnote={
                <>
                  An <span className="text-gray-400">index</span> (not a calibrated probability) of how far the AI/Tech
                  bull regime has deteriorated. Mostly coincident — it shortens reaction time, it doesn&apos;t predict
                  the turn.
                  {monitor.data.date && <> As of {monitor.data.date}.</>}
                  {monitor.data.inputs && (monitor.data.inputs.vix != null || monitor.data.inputs.hy_oas != null) && (
                    <span className="ml-1 text-gray-600">
                      VIX {monitor.data.inputs.vix ?? '—'} · HY OAS {monitor.data.inputs.hy_oas ?? '—'}
                    </span>
                  )}
                </>
              }
            />
            <ScoreGauge
              label="Early warning · breadth divergence"
              score={monitor.data.early_warning?.score}
              band={monitor.data.early_warning?.band}
              trend={monitor.data.early_warning}
              footnote={
                <>
                  Breadth narrowing while price holds. In the event study it led ~6 weeks on 7/11 past drawdowns, but
                  it&apos;s noisy (≈2× base rate) and blind to shocks. Observational — separate from the index, not
                  wired into trades.
                </>
              }
            />
          </div>
          <Suspense fallback={<SkeletonCard className="h-80" />}>
            <RegimeQuadrant />
          </Suspense>
          <Suspense fallback={<SkeletonCard className="h-72" />}>
            <ScoreHistoryChart />
          </Suspense>
          {monitor.data.breakdown && <Breakdown breakdown={monitor.data.breakdown} />}
        </>
      )}

      <EventStudyPanel />

      {isAdmin && <AdminControls />}
    </div>
  );
}
