import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query';
import { getUsageDaily, type UsageBucket, type UsageCounters, type UsageGroupBy } from '../../api';

type Preset = 'last7' | 'last30' | 'last90' | 'ytd' | 'custom';
type Gran = 'day' | 'week' | 'month';

/** Viewer's local calendar today as 'YYYY-MM-DD' (the server re-buckets UTC
 *  hours into this local frame via tzOffset). */
function localToday(): string {
  const d = new Date();
  const mm = String(d.getMonth() + 1).padStart(2, '0');
  const dd = String(d.getDate()).padStart(2, '0');
  return `${d.getFullYear()}-${mm}-${dd}`;
}
/** Minutes east of UTC for the viewer (JST → +540). */
function localTzOffset(): number {
  return -new Date().getTimezoneOffset();
}
function shiftDay(day: string, delta: number): string {
  const x = new Date(`${day}T00:00:00.000Z`);
  x.setUTCDate(x.getUTCDate() + delta);
  return x.toISOString().slice(0, 10);
}
function yearStart(): string {
  return `${new Date().getFullYear()}-01-01`;
}
function fmtTokens(n: number): string {
  if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`;
  if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
  return String(n);
}
function total(c: UsageCounters): number {
  return c.tokensIn + c.tokensOut;
}
function zeroCounters(): UsageCounters {
  return { tokensIn: 0, tokensOut: 0, requests: 0 };
}
/** Total tokens across all series segments in a bucket. */
function bucketTotal(b: UsageBucket): number {
  let s = 0;
  for (const k of Object.keys(b.segments)) s += total(b.segments[k]);
  return s;
}

// Mirror the server's bucket keys (usage-api.ts) so we can fill empty buckets
// and keep the chart's time axis linear instead of index-based.
function isoWeekKey(day: string): string {
  const d = new Date(`${day}T00:00:00.000Z`);
  const dayNum = (d.getUTCDay() + 6) % 7;
  d.setUTCDate(d.getUTCDate() - dayNum + 3);
  const firstThursday = new Date(Date.UTC(d.getUTCFullYear(), 0, 4));
  const firstDayNum = (firstThursday.getUTCDay() + 6) % 7;
  firstThursday.setUTCDate(firstThursday.getUTCDate() - firstDayNum + 3);
  const week = 1 + Math.round((d.getTime() - firstThursday.getTime()) / (7 * 86_400_000));
  return `${d.getUTCFullYear()}-W${String(week).padStart(2, '0')}`;
}
function bucketKey(day: string, g: Gran): string {
  if (g === 'month') return day.slice(0, 7);
  if (g === 'week') return isoWeekKey(day);
  return day;
}
/** Ordered, gap-free bucket list across [from, to] at the chosen granularity. */
function denseBuckets(series: UsageBucket[], from: string, to: string, g: Gran): UsageBucket[] {
  const byKey = new Map(series.map((b) => [b.bucket, b]));
  const out: UsageBucket[] = [];
  const seen = new Set<string>();
  let day = from;
  let guard = 0;
  while (day <= to && guard++ < 2000) {
    const key = bucketKey(day, g);
    if (!seen.has(key)) {
      seen.add(key);
      out.push(byKey.get(key) ?? { bucket: key, segments: {} });
    }
    day = shiftDay(day, 1);
  }
  return out;
}

function rangeFor(preset: Preset, customFrom: string, customTo: string): { from: string; to: string } {
  const to = localToday();
  switch (preset) {
    case 'last7': return { from: shiftDay(to, -6), to };
    case 'last90': return { from: shiftDay(to, -89), to };
    case 'ytd': return { from: yearStart(), to };
    case 'custom': return { from: customFrom || shiftDay(to, -29), to: customTo || to };
    case 'last30':
    default: return { from: shiftDay(to, -29), to };
  }
}

const CHART_H = 176; // px — stacked-bar plot height (was the h-44 / 11rem class)
const GW = '#6366f1'; // indigo-500 — gateway (source axis)
const DR = '#22c55e'; // green-500 — direct (source axis)
const OTHER_COLOR = '#94a3b8'; // slate-400 — folded 'other' bucket
// Distinct palette for dynamic axes (model / route / user / org).
const PALETTE = [
  '#6366f1', '#22c55e', '#f59e0b', '#ec4899', '#06b6d4', '#a855f7',
  '#ef4444', '#14b8a6', '#eab308', '#3b82f6', '#f97316', '#8b5cf6',
];

function colorFor(key: string, index: number, groupBy: UsageGroupBy): string {
  if (key === 'other') return OTHER_COLOR;
  if (groupBy === 'source') return key === 'gateway' ? GW : DR;
  return PALETTE[index % PALETTE.length];
}

/** i18next t with interpolation support (widened from the bare key signature). */
type TFn = (key: string, opts?: Record<string, unknown>) => string;

const GROUP_BYS: UsageGroupBy[] = ['source', 'model', 'route', 'user', 'org'];

export function UsagePage() {
  const { t } = useTranslation('usage');
  const [preset, setPreset] = useState<Preset>('last30');
  const [granularity, setGranularity] = useState<Gran>('day');
  const [groupBy, setGroupBy] = useState<UsageGroupBy>('source');
  const [customFrom, setCustomFrom] = useState('');
  const [customTo, setCustomTo] = useState('');

  const { from, to } = rangeFor(preset, customFrom, customTo);
  const tzOffset = localTzOffset();
  // Client-side guard: don't fire a request the server would 400 on; show an
  // inline message instead of a generic error.
  const customInvalid = preset === 'custom' && !!customFrom && !!customTo && customFrom > customTo;

  const { data, isLoading, error } = useQuery({
    queryKey: ['usage-daily', from, to, granularity, groupBy, tzOffset],
    queryFn: () => getUsageDaily({ from, to, granularity, groupBy, tzOffset }),
    enabled: !customInvalid,
  });

  const presets: Preset[] = ['last7', 'last30', 'last90', 'ytd', 'custom'];
  const grans: Gran[] = ['day', 'week', 'month'];

  const series: UsageBucket[] = data?.series ?? [];
  const keys = data?.keys ?? [];
  // Gap-free buckets so the bar/line x-axis is time-linear, not index-based.
  const dense = useMemo(
    () => (data ? denseBuckets(series, data.from, data.to, granularity) : []),
    [series, data, granularity],
  );
  const maxBucket = useMemo(
    () => dense.reduce((m, b) => Math.max(m, bucketTotal(b)), 0),
    [dense],
  );
  // Per-series running cumulative (one independent line each).
  // cumByKey[key][i] = that series' cumulative tokens through bucket i. The
  // shared y-axis is the largest single-series final cumulative.
  const { cumByKey, maxCumulative, grandCumulative } = useMemo(() => {
    const run: Record<string, number> = {};
    const acc: Record<string, number[]> = {};
    for (const k of keys) { run[k] = 0; acc[k] = []; }
    for (const b of dense) {
      for (const k of keys) {
        run[k] += total(b.segments[k] ?? zeroCounters());
        acc[k].push(run[k]);
      }
    }
    const maxLine = keys.reduce((m, k) => Math.max(m, run[k] ?? 0), 0);
    const grand = keys.reduce((s, k) => s + (run[k] ?? 0), 0);
    return { cumByKey: acc, maxCumulative: maxLine, grandCumulative: grand };
  }, [dense, keys]);

  // Display label for a series key (resolve user ids, localize sentinels).
  const keyLabel = (key: string): string => {
    if (data?.labels?.[key]) return data.labels[key];
    if (groupBy === 'source') return t(key === 'gateway' ? 'chart.legendGateway' : 'chart.legendDirect');
    if (key === 'no-org') return t('axis.noOrg');
    if (key === 'other') return t('axis.other');
    return key;
  };

  return (
    <div className="flex-1 min-h-0 overflow-auto">
      <div className="max-w-5xl mx-auto p-6 space-y-5">
        <header>
          <h1 className="text-lg font-semibold text-slate-800 dark:text-slate-100">{t('title')}</h1>
          <p className="text-[13px] text-slate-500 dark:text-slate-400 mt-1">{t('subtitle')}</p>
        </header>

        {/* Controls: range presets + custom + granularity */}
        <div className="flex flex-wrap items-center gap-3">
          <div className="flex gap-1">
            {presets.map((p) => (
              <button
                key={p}
                onClick={() => setPreset(p)}
                className={`px-2.5 py-1 text-xs rounded-md border transition-colors ${
                  preset === p
                    ? 'bg-accent text-white border-accent'
                    : 'border-hairline text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800'
                }`}
              >
                {t(`range.${p}`)}
              </button>
            ))}
          </div>
          {preset === 'custom' && (
            <div className="flex items-center gap-1.5 text-xs">
              <input type="date" value={customFrom} onChange={(e) => setCustomFrom(e.target.value)}
                aria-invalid={customInvalid}
                className="border border-hairline rounded px-1.5 py-1 bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200 [color-scheme:light] dark:[color-scheme:dark]" />
              <span className="text-slate-400">→</span>
              <input type="date" value={customTo} onChange={(e) => setCustomTo(e.target.value)}
                aria-invalid={customInvalid}
                className="border border-hairline rounded px-1.5 py-1 bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200 [color-scheme:light] dark:[color-scheme:dark]" />
            </div>
          )}
          <div className="flex items-center gap-1 ml-auto">
            <span className="text-xs text-slate-400 mr-1">{t('granularity.label')}</span>
            {grans.map((g) => (
              <button
                key={g}
                onClick={() => setGranularity(g)}
                className={`px-2 py-1 text-xs rounded-md border transition-colors ${
                  granularity === g
                    ? 'bg-accent text-white border-accent'
                    : 'border-hairline text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800'
                }`}
              >
                {t(`granularity.${g}`)}
              </button>
            ))}
          </div>
        </div>

        {/* Group-by selector — the breakdown dimension for every chart below. */}
        <div className="flex items-center gap-1 flex-wrap">
          <span className="text-xs text-slate-400 mr-1">{t('groupBy.label')}</span>
          {GROUP_BYS.map((gb) => (
            <button
              key={gb}
              onClick={() => setGroupBy(gb)}
              className={`px-2 py-1 text-xs rounded-md border transition-colors ${
                groupBy === gb
                  ? 'bg-accent text-white border-accent'
                  : 'border-hairline text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800'
              }`}
            >
              {t(`groupBy.${gb}`)}
            </button>
          ))}
        </div>

        {customInvalid && <div className="text-sm text-amber-600 dark:text-amber-400">{t('range.invalid')}</div>}
        {!customInvalid && isLoading && <div className="text-sm text-slate-400 italic">{t('loading')}</div>}
        {!customInvalid && error && <div className="text-sm text-red-600">{t('error')}</div>}

        {!customInvalid && data && (
          <>
            <TotalsCards totals={data.totals} keys={keys} groupBy={groupBy} keyLabel={keyLabel} t={t} />

            {series.length === 0 || keys.length === 0 ? (
              <div className="border border-hairline rounded-lg p-8 text-center text-sm text-slate-400 italic">
                {t('empty')}
              </div>
            ) : (
              <>
                <SeriesLegend keys={keys} groupBy={groupBy} keyLabel={keyLabel} />
                <StackedBars series={dense} keys={keys} maxBucket={maxBucket} groupBy={groupBy} keyLabel={keyLabel} t={t} />
                <CumulativeLines series={dense} keys={keys} cumByKey={cumByKey} max={maxCumulative} grand={grandCumulative} groupBy={groupBy} t={t} />
              </>
            )}

            {data.byUser && data.byUser.length > 0 && <ByUserTable rows={data.byUser} t={t} />}
          </>
        )}
      </div>
    </div>
  );
}

function TotalsCards({
  totals, keys, groupBy, keyLabel, t,
}: {
  totals: Record<string, UsageCounters>;
  keys: string[];
  groupBy: UsageGroupBy;
  keyLabel: (k: string) => string;
  t: TFn;
}) {
  const combined: UsageCounters = { tokensIn: 0, tokensOut: 0, requests: 0 };
  for (const k of keys) {
    const c = totals[k] ?? zeroCounters();
    combined.tokensIn += c.tokensIn;
    combined.tokensOut += c.tokensOut;
    combined.requests += c.requests;
  }
  const card = (label: string, c: UsageCounters, dot?: string) => (
    <div className="border border-hairline rounded-lg p-3">
      <div className="flex items-center gap-1.5 mb-2">
        {dot && <span className="inline-block w-2.5 h-2.5 rounded-sm" style={{ background: dot }} />}
        <span className="text-xs font-medium text-slate-500 uppercase tracking-wide truncate">{label}</span>
      </div>
      <div className="grid grid-cols-3 gap-2 text-sm">
        <div>
          <div className="text-[11px] text-slate-400">{t('totals.input')}</div>
          <div className="font-mono">{fmtTokens(c.tokensIn)}</div>
        </div>
        <div>
          <div className="text-[11px] text-slate-400">{t('totals.output')}</div>
          <div className="font-mono">{fmtTokens(c.tokensOut)}</div>
        </div>
        <div>
          <div className="text-[11px] text-slate-400">{t('totals.requests')}</div>
          <div className="font-mono">{c.requests.toLocaleString()}</div>
        </div>
      </div>
    </div>
  );
  // Combined card always; for the source axis keep the familiar gateway/direct
  // pair. For dynamic axes show the top few series so the cards don't explode.
  const detailKeys = groupBy === 'source' ? keys : keys.slice(0, 2);
  return (
    <div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
      {card(t('totals.combined'), combined)}
      {detailKeys.map((k, i) => card(keyLabel(k), totals[k] ?? zeroCounters(), colorFor(k, i, groupBy)))}
    </div>
  );
}

function SeriesLegend({
  keys, groupBy, keyLabel,
}: {
  keys: string[];
  groupBy: UsageGroupBy;
  keyLabel: (k: string) => string;
}) {
  return (
    <div className="flex items-center gap-3 flex-wrap text-[11px] text-slate-500">
      {keys.map((k, i) => (
        <span key={k} className="flex items-center gap-1">
          <span className="inline-block w-2.5 h-2.5 rounded-sm" style={{ background: colorFor(k, i, groupBy) }} />
          <span className="truncate max-w-[160px]">{keyLabel(k)}</span>
        </span>
      ))}
    </div>
  );
}

function StackedBars({
  series, keys, maxBucket, groupBy, keyLabel, t,
}: {
  series: UsageBucket[];
  keys: string[];
  maxBucket: number;
  groupBy: UsageGroupBy;
  keyLabel: (k: string) => string;
  t: TFn;
}) {
  const grand = series.reduce((s, b) => s + bucketTotal(b), 0);
  const ariaLabel = t('chart.barsAria', { title: t('chart.tokensTitle'), total: fmtTokens(grand), count: series.length });
  return (
    <div className="border border-hairline rounded-lg p-4">
      <div className="flex items-center justify-between mb-3">
        <span className="text-xs font-medium text-slate-500 uppercase tracking-wide">{t('chart.tokensTitle')}</span>
      </div>
      {/* Bar heights are computed in pixels off CHART_H, not percentages: a
          percentage-height chain inside this flex column collapses to zero
          because the column items aren't given a definite height. */}
      <div className="flex items-end gap-1" style={{ height: CHART_H }} role="img" aria-label={ariaLabel}>
        {series.map((b) => {
          const sum = bucketTotal(b);
          const barPx = maxBucket > 0 ? Math.max((sum / maxBucket) * CHART_H, sum > 0 ? 2 : 0) : 0;
          return (
            <div key={b.bucket} className="flex-1 min-w-0 flex flex-col items-center group relative">
              <div className="w-full rounded-t-sm overflow-hidden flex flex-col" style={{ height: barPx }}>
                {keys.map((k, i) => {
                  const v = total(b.segments[k] ?? zeroCounters());
                  const segPx = sum > 0 ? (v / sum) * barPx : 0;
                  return segPx > 0 ? <div key={k} style={{ height: segPx, background: colorFor(k, i, groupBy) }} /> : null;
                })}
              </div>
              {/* Tooltip (decorative — chart summary is exposed via aria-label) */}
              <div aria-hidden="true" className="pointer-events-none absolute bottom-full mb-1 hidden group-hover:block z-10 whitespace-nowrap bg-slate-800 text-white text-[10px] rounded px-1.5 py-1 shadow">
                <div className="font-mono mb-0.5">{b.bucket}</div>
                {keys.map((k, i) => {
                  const v = total(b.segments[k] ?? zeroCounters());
                  return v > 0 ? (
                    <div key={k}><span style={{ color: colorFor(k, i, groupBy) }}>■</span> {keyLabel(k)}: {fmtTokens(v)}</div>
                  ) : null;
                })}
              </div>
            </div>
          );
        })}
      </div>
      <div className="flex justify-between mt-2 text-[10px] text-slate-400 font-mono">
        <span>{series[0]?.bucket}</span>
        <span>{series[series.length - 1]?.bucket}</span>
      </div>
    </div>
  );
}

function CumulativeLines({
  series, keys, cumByKey, max, grand, groupBy, t,
}: {
  series: UsageBucket[];
  keys: string[];
  cumByKey: Record<string, number[]>;
  max: number;
  grand: number;
  groupBy: UsageGroupBy;
  t: TFn;
}) {
  const W = 600;
  const H = 140;
  const pad = 4;
  const n = series.length;
  const xAt = (i: number) => (n <= 1 ? W / 2 : pad + (i / (n - 1)) * (W - 2 * pad));
  const yAt = (v: number) => (max > 0 ? H - pad - (v / max) * (H - 2 * pad) : H - pad);
  const ariaLabel = t('chart.lineAria', { total: fmtTokens(grand) });
  return (
    <div className="border border-hairline rounded-lg p-4">
      <div className="flex items-baseline justify-between mb-3">
        <span className="text-xs font-medium text-slate-500 uppercase tracking-wide">{t('chart.cumulativeTitle')}</span>
        <span className="text-[11px] text-slate-500 font-mono">{t('chart.total', { value: fmtTokens(grand) })}</span>
      </div>
      <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none" className="w-full h-36" role="img" aria-label={ariaLabel}>
        {keys.map((k, ki) => {
          const vals = cumByKey[k] ?? [];
          const color = colorFor(k, ki, groupBy);
          if (n === 1) {
            return <circle key={k} cx={xAt(0)} cy={yAt(vals[0] ?? 0)} r={3} fill={color} vectorEffect="non-scaling-stroke" />;
          }
          const pts = vals.map((v, i) => `${xAt(i).toFixed(1)},${yAt(v).toFixed(1)}`).join(' ');
          return <polyline key={k} points={pts} fill="none" stroke={color} strokeWidth={2} vectorEffect="non-scaling-stroke" />;
        })}
      </svg>
      <div className="flex justify-between mt-1 text-[10px] text-slate-400 font-mono">
        <span>{series[0]?.bucket}</span>
        <span>{series[series.length - 1]?.bucket}</span>
      </div>
    </div>
  );
}

function ByUserTable({ rows, t }: { rows: Array<{ userId: string; displayName: string } & UsageCounters>; t: TFn }) {
  // Localize the sentinels; real users show their resolved name with the id as
  // a secondary line.
  const label = (r: { userId: string; displayName: string }): { primary: string; secondary?: string } => {
    if (r.userId === 'local') return { primary: t('byUser.local') };
    if (r.userId === 'system') return { primary: t('byUser.system') };
    return { primary: r.displayName, secondary: r.displayName === r.userId ? undefined : r.userId };
  };
  return (
    <div className="border border-hairline rounded-lg p-4">
      <div className="text-xs font-medium text-slate-500 uppercase tracking-wide mb-3">{t('byUser.title')}</div>
      <table className="w-full text-sm">
        <thead>
          <tr className="text-[11px] text-slate-400 text-left">
            <th className="font-medium pb-1">{t('byUser.user')}</th>
            <th className="font-medium pb-1 text-right">{t('byUser.input')}</th>
            <th className="font-medium pb-1 text-right">{t('byUser.output')}</th>
            <th className="font-medium pb-1 text-right">{t('byUser.requests')}</th>
          </tr>
        </thead>
        <tbody>
          {rows.map((r) => {
            const l = label(r);
            return (
              <tr key={r.userId} className="border-t border-hairline">
                <td className="py-1.5 text-slate-600 dark:text-slate-300">
                  <span>{l.primary}</span>
                  {l.secondary && <span className="ml-1.5 text-[11px] text-slate-400 font-mono">{l.secondary}</span>}
                </td>
                <td className="py-1.5 text-right font-mono">{fmtTokens(r.tokensIn)}</td>
                <td className="py-1.5 text-right font-mono">{fmtTokens(r.tokensOut)}</td>
                <td className="py-1.5 text-right font-mono">{r.requests.toLocaleString()}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}
