// screens-bets.jsx — 注单管理（admin bets explorer）
// 实时注单流 · 风控筛查 · 异常注单作废 · CSV 导出
//
// Pages a paginated, filtered table from /api/admin/bets and lets ops
// drill into a single bet for picks / period draw / wallet balance, and
// void a pending bet with an audit trail.

const _BETS_API = window.NebulaAdmin.API_BASE();

const STATUS_TONE = {
  pending: { cls: 'info',    bg: 'rgba(99,102,241,0.06)' },
  win:     { cls: 'success', bg: 'rgba(34,197,94,0.06)' },
  lose:    { cls: 'normal',  bg: 'transparent' },
  refund:  { cls: 'warning', bg: 'rgba(148,163,184,0.06)' },
  void:    { cls: 'danger',  bg: 'rgba(148,163,184,0.10)' },
};

const LOTTERY_OPTS  = ['', 'cqssc', 'k3', 'pk10', 'lhc'];
const STATUS_OPTS   = ['', 'pending', 'win', 'lose', 'refund', 'void'];
const LIMIT_OPTS    = [20, 50, 100, 200];

function relTime(iso, t) {
  if (!iso) return '—';
  const d = new Date(iso);
  if (isNaN(d.getTime())) return '—';
  const sec = Math.max(0, Math.floor((Date.now() - d.getTime()) / 1000));
  if (sec < 60)    return t.tx(sec + ' 秒前', sec + 's ago');
  if (sec < 3600)  return t.tx(Math.floor(sec / 60) + ' 分前', Math.floor(sec / 60) + 'm ago');
  if (sec < 86400) return t.tx(Math.floor(sec / 3600) + ' 时前', Math.floor(sec / 3600) + 'h ago');
  return d.toLocaleString('zh-CN', { hour12: false });
}

function picksCompact(picks) {
  if (!picks) return '—';
  try {
    if (picks.side) return String(picks.side);
    const parts = [];
    for (const k of Object.keys(picks)) {
      const v = picks[k];
      if (Array.isArray(v)) parts.push(k + ':' + v.join(','));
      else parts.push(k + ':' + v);
    }
    const s = parts.join(' / ');
    return s.length > 28 ? s.slice(0, 28) + '…' : s;
  } catch { return JSON.stringify(picks).slice(0, 28); }
}

function StatusChip({ status, t }) {
  const tone = STATUS_TONE[status] || STATUS_TONE.lose;
  const label = ({
    pending: t.tx('待开', 'Pending'),
    win:     t.tx('中奖', 'Win'),
    lose:    t.tx('未中', 'Lose'),
    refund:  t.tx('退款', 'Refund'),
    void:    t.tx('作废', 'Void'),
  })[status] || status;
  return <span className={'badge ' + tone.cls} style={{ fontSize: 10 }}>{label}</span>;
}

function KPI({ label, value, tone }) {
  return (
    <div className="card" style={{ padding: 14, minWidth: 160, flex: 1 }}>
      <div className="text-muted" style={{ fontSize: 11, marginBottom: 4 }}>{label}</div>
      <div className="tabular" style={{ fontSize: 20, fontWeight: 700, color: tone || 'var(--text)' }}>{value}</div>
    </div>
  );
}

function BetsScreen({ t, push }) {
  // ── filter state ──────────────────────────────────────────────────────────
  const [filters, setFilters] = React.useState({
    lottery: '', status: '', play_id: '', user_id: '', period: '',
    min_stake: '', max_stake: '', from: '', to: '', big_win: false,
  });
  const [page, setPage]   = React.useState(1);
  const [limit, setLimit] = React.useState(50);

  const [data, setData]     = React.useState(null);
  const [loading, setLoad]  = React.useState(false);
  const [err, setErr]       = React.useState(null);

  // expanded row + its detail payload
  const [openId, setOpenId]   = React.useState(null);
  const [detail, setDetail]   = React.useState(null);

  // void modal
  const [voidTarget, setVoidTarget] = React.useState(null);
  const [voidReason, setVoidReason] = React.useState('');

  // auto-refresh
  const [autoRefresh, setAuto] = React.useState(filters.status === 'pending');

  const buildQuery = React.useCallback(() => {
    const q = new URLSearchParams();
    Object.keys(filters).forEach(k => {
      if (k === 'big_win') {
        if (filters.big_win) q.set('min_payout', '1000');
      } else if (filters[k] !== '' && filters[k] != null) {
        q.set(k, filters[k]);
      }
    });
    q.set('page', String(page));
    q.set('limit', String(limit));
    return q.toString();
  }, [filters, page, limit]);

  const load = React.useCallback(async () => {
    setLoad(true); setErr(null);
    try {
      const j = await window.NebulaAdmin.adminFetch('/api/admin/bets?' + buildQuery());
      setData(j);
    } catch (e) {
      setErr(e); push(t.tx('加载失败：', 'Load failed: ') + (e.status || e.message));
    } finally { setLoad(false); }
  }, [buildQuery, push, t]);

  React.useEffect(() => { load(); }, [load]);

  // Default auto-refresh ON if filtering to pending, OFF otherwise.
  React.useEffect(() => { setAuto(filters.status === 'pending'); }, [filters.status]);

  React.useEffect(() => {
    if (!autoRefresh) return;
    const id = setInterval(load, 5000);
    return () => clearInterval(id);
  }, [autoRefresh, load]);

  const setF = (k, v) => { setFilters(f => ({ ...f, [k]: v })); setPage(1); };
  const reset = () => {
    setFilters({ lottery: '', status: '', play_id: '', user_id: '', period: '',
                 min_stake: '', max_stake: '', from: '', to: '', big_win: false });
    setPage(1);
  };

  const toggleRow = async (row) => {
    if (openId === row.id) { setOpenId(null); setDetail(null); return; }
    setOpenId(row.id); setDetail(null);
    try {
      const j = await window.NebulaAdmin.adminFetch('/api/admin/bets/' + encodeURIComponent(row.id));
      setDetail(j);
    } catch (e) { push(t.tx('详情加载失败', 'Detail load failed')); }
  };

  const doExport = () => {
    const q = buildQuery();
    const token = window.NebulaAdmin.getToken();
    // gh: server requires Bearer; do it via fetch+blob to attach the header.
    push(t.tx('导出中…', 'Exporting…'));
    fetch(_BETS_API + '/api/admin/bets/export?' + q, {
      headers: { Authorization: 'Bearer ' + token },
    }).then(async r => {
      if (!r.ok) {
        const j = await r.json().catch(() => ({}));
        if (r.status === 413) push(t.tx('数据超出 1 万条，请收窄筛选', 'Over 10k rows — narrow filter'));
        else push(t.tx('导出失败', 'Export failed') + ': ' + (j.error || r.status));
        return;
      }
      const blob = await r.blob();
      const url  = URL.createObjectURL(blob);
      const a    = document.createElement('a');
      a.href = url; a.download = 'bets-' + Date.now() + '.csv';
      document.body.appendChild(a); a.click(); a.remove();
      URL.revokeObjectURL(url);
      push(t.tx('已导出', 'Exported'));
    }).catch(e => push(t.tx('导出失败', 'Export failed') + ': ' + e.message));
  };

  const doVoid = async () => {
    if (!voidTarget) return;
    if (!voidReason.trim()) { push(t.tx('请填写作废原因', 'Reason required')); return; }
    try {
      await window.NebulaAdmin.adminFetch('/api/admin/bets/' + encodeURIComponent(voidTarget.id) + '/void', {
        method: 'POST',
        body: JSON.stringify({ reason: voidReason.trim() }),
      });
      push(t.tx('已作废并退款 ¥', 'Voided & refunded ¥') + voidTarget.total_stake);
      setVoidTarget(null); setVoidReason('');
      load();
    } catch (e) {
      const code = e.body && e.body.error;
      if (code === 'already_settled') push(t.tx('已结算，无法作废', 'Already settled, cannot void'));
      else if (code === 'already_voided') push(t.tx('已作废', 'Already voided'));
      else push(t.tx('作废失败：', 'Void failed: ') + (code || e.status || e.message));
    }
  };

  const rows = (data && data.rows) || [];
  const summary = (data && data.summary) || {};
  const total = (data && data.total) || 0;
  const totalPages = Math.max(1, Math.ceil(total / limit));

  return (
    <div className="stack">
      <div className="page-h">
        <div>
          <h1>{t.tx('注单管理', 'Bets Mgmt')}</h1>
          <div className="sub">{t.tx('实时注单流 · 风控筛查 · 异常注单作废', 'Realtime stream · Risk inspection · Void anomalies')}</div>
        </div>
        <div className="actions">
          <label style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 11, color: 'var(--text-muted)' }}>
            <input type="checkbox" checked={autoRefresh} onChange={e => setAuto(e.target.checked)} />
            {t.tx('自动刷新 5s', 'Auto refresh 5s')}
          </label>
          <button className="btn btn-sm" onClick={load} disabled={loading}>
            {loading ? t.tx('加载…', 'Loading…') : t.tx('刷新', 'Refresh')}
          </button>
          <button className="btn btn-sm" onClick={doExport}>{t.tx('导出 CSV', 'Export CSV')}</button>
          <button className="btn btn-sm" onClick={reset}>{t.tx('重置筛选', 'Reset')}</button>
        </div>
      </div>

      {/* Filter bar */}
      <div className="card" style={{ padding: 12 }}>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' }}>
          <FilterField label={t.tx('彩种', 'Lottery')}>
            <select className="input" value={filters.lottery} onChange={e => setF('lottery', e.target.value)}>
              {LOTTERY_OPTS.map(v => <option key={v} value={v}>{v === '' ? t.tx('全部', 'All') : v}</option>)}
            </select>
          </FilterField>
          <FilterField label={t.tx('状态', 'Status')}>
            <select className="input" value={filters.status} onChange={e => setF('status', e.target.value)}>
              {STATUS_OPTS.map(v => <option key={v} value={v}>{v === '' ? t.tx('全部', 'All') : v}</option>)}
            </select>
          </FilterField>
          <FilterField label={t.tx('玩法', 'Play')}>
            <input className="input" placeholder="play_id" value={filters.play_id}
                   onChange={e => setF('play_id', e.target.value)} style={{ width: 130 }} />
          </FilterField>
          <FilterField label={t.tx('用户', 'User')}>
            <input className="input" placeholder="U-xxxx" value={filters.user_id}
                   onChange={e => setF('user_id', e.target.value)} style={{ width: 130 }} />
          </FilterField>
          <FilterField label={t.tx('期号', 'Period')}>
            <input className="input" placeholder="20261124369" value={filters.period}
                   onChange={e => setF('period', e.target.value)} style={{ width: 130 }} />
          </FilterField>
          <FilterField label={t.tx('投注范围', 'Stake')}>
            <input className="input" placeholder="min" value={filters.min_stake}
                   onChange={e => setF('min_stake', e.target.value)} style={{ width: 70 }} />
            <span style={{ fontSize: 11, color: 'var(--text-faint)', margin: '0 4px' }}>—</span>
            <input className="input" placeholder="max" value={filters.max_stake}
                   onChange={e => setF('max_stake', e.target.value)} style={{ width: 70 }} />
          </FilterField>
          <FilterField label={t.tx('时间', 'Time')}>
            <DateRangePicker
              t={t}
              from={(filters.from || '').slice(0, 10)}
              to={(filters.to || '').slice(0, 10)}
              onChange={(f, tt) => {
                setFilters(s => ({
                  ...s,
                  from: f ? f + 'T00:00:00Z' : '',
                  to:   tt ? tt + 'T23:59:59Z' : '',
                }));
                setPage(1);
              }}
              size="sm"
            />
          </FilterField>
          <label style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 11, color: 'var(--text-muted)' }}>
            <input type="checkbox" checked={filters.big_win} onChange={e => setF('big_win', e.target.checked)} />
            {t.tx('大奖 (≥¥1000)', 'Big win (≥¥1000)')}
          </label>
        </div>
      </div>

      {/* Summary KPIs */}
      <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
        <KPI label={t.tx('总注单数', 'Total bets')} value={total.toLocaleString()} />
        <KPI label={t.tx('总投注额', 'Total stake')} value={'¥' + (summary.total_stake || 0).toLocaleString()} />
        <KPI label={t.tx('总派彩', 'Total payout')} value={'¥' + (summary.total_payout || 0).toLocaleString()}
             tone="var(--warning)" />
        <KPI label={t.tx('RTP', 'RTP')}
             value={((summary.rtp || 0) * 100).toFixed(1) + '%'}
             tone={(summary.rtp || 0) > 1 ? 'var(--danger)' : 'var(--success)'} />
      </div>

      {/* Counts strip */}
      <div className="text-muted" style={{ fontSize: 11, display: 'flex', gap: 14, flexWrap: 'wrap', padding: '0 2px' }}>
        <span>{t.tx('中奖', 'Win')}: <b className="tabular">{summary.win_count || 0}</b></span>
        <span>{t.tx('未中', 'Lose')}: <b className="tabular">{summary.lose_count || 0}</b></span>
        <span>{t.tx('待开', 'Pending')}: <b className="tabular">{summary.pending_count || 0}</b></span>
        <span>{t.tx('退款', 'Refund')}: <b className="tabular">{summary.refund_count || 0}</b></span>
        <span>{t.tx('作废', 'Void')}: <b className="tabular">{summary.void_count || 0}</b></span>
      </div>

      {/* Table */}
      <div className="card">
        <div className="card-h">
          <h3>{t.tx('注单列表', 'Bets list')}</h3>
          <span className="meta">{t.tx('点行查看详情', 'Click row for details')}</span>
        </div>
        <div className="tbl-scroll">
        <table className="tbl tbl-stack">
          <thead>
            <tr>
              <th>{t.tx('时间', 'Time')}</th>
              <th>{t.tx('用户', 'User')}</th>
              <th>{t.tx('彩种', 'Lottery')}</th>
              <th>{t.tx('期号', 'Period')}</th>
              <th>{t.tx('玩法', 'Play')}</th>
              <th>{t.tx('选号', 'Picks')}</th>
              <th className="right">{t.tx('注数', 'Notes')}</th>
              <th className="right">{t.tx('投注', 'Stake')}</th>
              <th className="right">{t.tx('倍数', 'Mul')}</th>
              <th>{t.tx('状态', 'Status')}</th>
              <th className="right">{t.tx('派彩', 'Payout')}</th>
              <th>{t.tx('操作', 'Actions')}</th>
            </tr>
          </thead>
          <tbody>
            {rows.length === 0 && (
              <tr><td colSpan={12} style={{ textAlign: 'center', padding: 24, color: 'var(--text-faint)', fontSize: 12 }}>
                {loading ? t.tx('加载中…', 'Loading…') : (err ? t.tx('加载失败', 'Load failed') : t.tx('暂无数据', 'No data'))}
              </td></tr>
            )}
            {rows.map(r => {
              const tone = STATUS_TONE[r.status] || STATUS_TONE.lose;
              const expanded = openId === r.id;
              return (
                <React.Fragment key={r.id}>
                  <tr onClick={() => toggleRow(r)}
                      style={{ background: expanded ? 'var(--bg-inset)' : tone.bg, cursor: 'pointer' }}>
                    <td data-label={t.tx('时间','Time')} className="text-mono" style={{ fontSize: 10 }} title={r.placed_at}>{relTime(r.placed_at, t)}</td>
                    <td data-label={t.tx('用户','User')} style={{ fontSize: 11 }}>
                      <div className="text-mono" style={{ fontSize: 10 }}>{r.user_id}</div>
                      <div className="text-faint" style={{ fontSize: 10 }}>{r.user_name || '—'}</div>
                    </td>
                    <td data-label={t.tx('彩种','Lottery')}><span className="chip" style={{ fontSize: 10 }}>{r.lottery}</span></td>
                    <td data-label={t.tx('期号','Period')} className="text-mono" style={{ fontSize: 10 }}>{String(r.period).slice(-6)}</td>
                    <td data-label={t.tx('玩法','Play')} className="text-mono" style={{ fontSize: 10 }}>{r.play_id}</td>
                    <td data-label={t.tx('选号','Picks')} className="text-faint" style={{ fontSize: 10 }} title={JSON.stringify(r.picks)}>{picksCompact(r.picks)}</td>
                    <td data-label={t.tx('注数','Notes')} className="right tabular">{r.note_count}</td>
                    <td data-label={t.tx('投注','Stake')} className="right tabular">¥{r.total_stake}</td>
                    <td data-label={t.tx('倍数','Mul')} className="right tabular">×{r.multiplier}</td>
                    <td data-label={t.tx('状态','Status')}><StatusChip status={r.status} t={t} /></td>
                    <td data-label={t.tx('派彩','Payout')} className="right tabular" style={{ color: r.payout > 0 ? 'var(--success)' : 'var(--text-faint)' }}>
                      {r.payout > 0 ? '¥' + r.payout : '—'}
                    </td>
                    <td data-label={t.tx('操作','Actions')} onClick={e => e.stopPropagation()}>
                      <button className="btn btn-sm" onClick={() => toggleRow(r)}>{expanded ? t.tx('收起', 'Hide') : t.tx('详情', 'Detail')}</button>
                      {' '}
                      <button className="btn btn-sm btn-danger"
                              disabled={r.status !== 'pending'}
                              onClick={() => { setVoidTarget(r); setVoidReason(''); }}>
                        {t.tx('作废', 'Void')}
                      </button>
                    </td>
                  </tr>
                  {expanded && (
                    <tr style={{ background: 'var(--bg-inset)' }}>
                      <td colSpan={12} style={{ padding: 14 }}>
                        {!detail ? (
                          <div className="text-faint" style={{ fontSize: 11 }}>{t.tx('详情加载中…', 'Loading detail…')}</div>
                        ) : (
                          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 16 }}>
                            <div>
                              <div className="text-muted" style={{ fontSize: 11, marginBottom: 4 }}>{t.tx('完整选号 JSON', 'Picks JSON')}</div>
                              <pre className="text-mono" style={{ fontSize: 10, background: 'var(--bg-card)', padding: 8, borderRadius: 4, margin: 0, overflow: 'auto', maxHeight: 200 }}>
                                {JSON.stringify(detail.picks, null, 2)}
                              </pre>
                            </div>
                            <div>
                              <div className="text-muted" style={{ fontSize: 11, marginBottom: 4 }}>{t.tx('开奖结果', 'Period draw')}</div>
                              {detail.period_draw ? (
                                <div>
                                  <div style={{ display: 'flex', gap: 6, marginBottom: 6 }}>
                                    {(detail.period_draw.numbers || []).map((n, i) => (
                                      <span key={i} className="text-mono"
                                            style={{ width: 24, height: 24, borderRadius: '50%',
                                                     background: i % 2 === 0 ? 'oklch(0.65 0.18 30)' : 'oklch(0.65 0.16 230)',
                                                     color: 'white', fontWeight: 700, fontSize: 11,
                                                     display: 'flex', alignItems: 'center', justifyContent: 'center' }}>{n}</span>
                                    ))}
                                  </div>
                                  <div className="text-faint" style={{ fontSize: 10 }}>
                                    {t.tx('结算时间', 'Settled at')}: {detail.settled_at ? new Date(detail.settled_at).toLocaleString('zh-CN') : '—'}
                                  </div>
                                </div>
                              ) : (
                                <div className="text-faint" style={{ fontSize: 11 }}>{t.tx('该期尚未开奖', 'Period not yet drawn')}</div>
                              )}
                            </div>
                            <div>
                              <div className="text-muted" style={{ fontSize: 11, marginBottom: 4 }}>{t.tx('用户钱包余额', 'User wallet balance')}</div>
                              <div className="tabular" style={{ fontSize: 18, fontWeight: 700 }}>
                                ¥{(detail.wallet_balance || 0).toLocaleString()}
                              </div>
                              <div className="text-faint" style={{ fontSize: 10, marginTop: 4 }}>
                                {t.tx('每注', 'Stake/note')}: ¥{r.stake_per_note} × {t.tx('倍数', 'mul')} {r.multiplier} × {r.note_count} = ¥{r.total_stake}
                              </div>
                            </div>
                          </div>
                        )}
                      </td>
                    </tr>
                  )}
                </React.Fragment>
              );
            })}
          </tbody>
        </table>
        </div>

        {/* Pagination */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: 12, fontSize: 11, color: 'var(--text-muted)' }}>
          <span>{t.tx('第', 'Page')} {page} / {totalPages} · {t.tx('共', 'total')} {total}</span>
          <div style={{ flex: 1 }} />
          <button className="btn btn-sm" disabled={page <= 1}        onClick={() => setPage(p => Math.max(1, p - 1))}>{t.tx('上一页', 'Prev')}</button>
          <button className="btn btn-sm" disabled={page >= totalPages} onClick={() => setPage(p => p + 1)}>{t.tx('下一页', 'Next')}</button>
          <select className="input" value={limit} onChange={e => { setLimit(parseInt(e.target.value, 10)); setPage(1); }} style={{ width: 70 }}>
            {LIMIT_OPTS.map(n => <option key={n} value={n}>{n}/{t.tx('页', 'p')}</option>)}
          </select>
        </div>
      </div>

      {/* Void modal */}
      {voidTarget && (
        <Modal open={!!voidTarget} onClose={() => setVoidTarget(null)} width={420}
               title={t.tx('作废注单', 'Void bet') + ' · ' + voidTarget.id}
               footer={
                 <>
                   <button className="btn btn-sm" onClick={() => setVoidTarget(null)}>{t.cancel}</button>
                   <button className="btn btn-sm btn-danger" onClick={doVoid}>{t.tx('确认作废并退款', 'Confirm void & refund')}</button>
                 </>
               }>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10, padding: 14 }}>
            <div className="text-muted" style={{ fontSize: 11 }}>
              {t.tx('该操作将退款 ¥', 'This will refund ¥')}{voidTarget.total_stake}
              {t.tx(' 至用户 ', ' to user ')}{voidTarget.user_id}
              {t.tx('，并记录审计。仅 pending 状态可作废。', '. Audited. Only pending bets can be voided.')}
            </div>
            <Field label={t.tx('作废原因（必填）', 'Reason (required)')}>
              <input className="input" autoFocus value={voidReason} onChange={e => setVoidReason(e.target.value)}
                     placeholder={t.tx('例如：客户投诉重复扣款 / 风控异常', 'e.g. duplicate charge / risk anomaly')} />
            </Field>
          </div>
        </Modal>
      )}
    </div>
  );
}

function FilterField({ label, children }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
      <span style={{ fontSize: 10, color: 'var(--text-faint)', textTransform: 'uppercase', letterSpacing: '.04em' }}>{label}</span>
      <div style={{ display: 'flex', alignItems: 'center' }}>{children}</div>
    </div>
  );
}

window.BetsScreen = BetsScreen;
