'use client';

import { useCallback, useEffect, useState } from 'react';
import type { ColumnsType } from 'antd/es/table';
import {
  App,
  Alert,
  Button,
  Card,
  Col,
  DatePicker,
  Divider,
  InputNumber,
  Modal,
  Popover,
  Row,
  Select,
  Space,
  Statistic,
  Table,
  Tag,
  Typography,
} from 'antd';
import {
  FallOutlined,
  InfoCircleOutlined,
  QuestionCircleOutlined,
  RiseOutlined,
} from '@ant-design/icons';
import dayjs, { type Dayjs } from 'dayjs';
import { api, errMsg } from '@/lib/api';
import { formatUtcTime } from '@/lib/format';
import type {
  AdRevenueDaily,
  AdRevenueRecord,
  AdRevenueReport,
  AdRevenueRow,
} from '@/lib/types';

const { RangePicker } = DatePicker;

// 广告类型标签
const TYPE_TAG: Record<string, { color: string; label: string }> = {
  reward_video: { color: 'blue', label: '激励视频' },
  feed: { color: 'purple', label: '信息流' },
  draw: { color: 'geekblue', label: 'Draw 信息流' },
  withdrawal_video: { color: 'gold', label: '提现激励视频' },
};

// 我们的应用环境标签
const APP_TAG: Record<string, { color: string; label: string }> = {
  prod: { color: 'green', label: '傻瓜比价(正式)' },
  test: { color: 'default', label: '测试应用' },
};

// 发奖状态 → 颜色 + 中文(含「不发金币的原因」:提前关闭/未满10秒/缺eCPM/次数超限)
const STATUS_TAG: Record<string, { color: string; label: string }> = {
  granted: { color: 'green', label: '已发' },
  capped: { color: 'orange', label: '次数超限' },
  ecpm_missing: { color: 'red', label: '缺 eCPM' },
  too_short: { color: 'gold', label: '未满10秒' },
  closed_early: { color: 'default', label: '提前关闭' },
};

const fmtFactorRange = (a: number | null, b: number | null) => {
  if (a == null) return '-';
  return a === b || b == null ? String(a) : `${a}→${b}`;
};

const fmtIndexRange = (a: number | null, b: number | null) => {
  if (a == null) return '-';
  return a === b ? String(a) : `${a}–${b}`;
};

// 金币计算公式(写死,展示用)——与后端 app/core/rewards.py 同源,改动以后端常量为准。
// 单次奖励(元) = (eCPM元 ÷ 1000) × 因子1(eCPM 元档) × 因子2(LT 累计条数);1 元 = 10000 金币,四舍五入取整。
const COIN_PER_YUAN = 10000;
// 因子1:按 eCPM(元/千次)判档,对应 AD_ECPM_FACTOR_TABLE
const ECPM_FACTOR_ROWS = [
  { key: '4', range: '> 400', factor: 0.6 },
  { key: '3', range: '201 – 400', factor: 0.4 },
  { key: '2', range: '101 – 200', factor: 0.3 },
  { key: '1', range: '0 – 100', factor: 0.1 },
];
// 因子2:按该账号累计第几条/份(不按天重置),对应 AD_LT_FACTOR_TABLE
const LT_FACTOR_ROWS = [
  { key: '1', lt: '第 1 条', factor: 2.0 },
  { key: '2', lt: '第 2 条', factor: 1.5 },
  { key: '3', lt: '第 3 条', factor: 1.3 },
  { key: '4', lt: '4 – 10 条', factor: 1.1 },
  { key: '5', lt: '第 11 条及以后', factor: 1.0 },
];

// 展开行(逐条事件展开)- 该条的发奖复算明细(还原金币审计的 eCPM/因子1/份数/LT/因子2 等列)
const DETAIL_COLUMNS: ColumnsType<AdRevenueRecord> = [
  { title: '时间', dataIndex: 'created_at', render: (v: string) => formatUtcTime(v), width: 165 },
  {
    title: '状态',
    dataIndex: 'status',
    width: 110,
    render: (s: string) => {
      const t = STATUS_TAG[s] ?? { color: 'default', label: s };
      return <Tag color={t.color}>{t.label}</Tag>;
    },
  },
  { title: 'eCPM（分）', dataIndex: 'ecpm', width: 90, render: (v: string | null) => v ?? '-' },
  { title: '因子1', dataIndex: 'ecpm_factor', width: 70, render: (v: number | null) => v ?? '-' },
  { title: '份数', dataIndex: 'units', width: 60 },
  {
    title: 'LT累计条数',
    key: 'lt_index',
    width: 100,
    render: (_: unknown, r: AdRevenueRecord) => fmtIndexRange(r.lt_index_start, r.lt_index_end),
  },
  {
    title: '因子2',
    key: 'lt_factor',
    width: 90,
    render: (_: unknown, r: AdRevenueRecord) => fmtFactorRange(r.lt_factor_start, r.lt_factor_end),
  },
  { title: '应发金币', dataIndex: 'expected_coin', width: 100, render: (v: number) => <b>{v}</b> },
  { title: '实发金币', dataIndex: 'actual_coin', width: 100 },
  {
    title: '一致',
    dataIndex: 'matched',
    width: 80,
    render: (m: boolean) => (m ? <Tag color="green">✓</Tag> : <Tag color="red">✗ 不符</Tag>),
  },
];

// 趋势图(纯 SVG,零依赖):柱=展示条数(左轴),线=预估收益元(右轴)。x 轴按传入点序。
const CHART_BAR = '#69b1ff';
const CHART_LINE = '#fa8c16';

interface TrendPoint {
  label: string; // x 轴刻度文案(小时数 / MM-DD)
  tip: string; // hover 原生 tooltip 全文
  impressions: number;
  revenue: number;
}

// 按小时聚合(0–23),数据源是 items(被 limit 截断时会偏少,调用处给警告)
function aggregateHourly(rows: AdRevenueRow[]): TrendPoint[] {
  const byHour = Array.from({ length: 24 }, (_, h) => ({ hour: h, impressions: 0, revenue: 0 }));
  for (const r of rows) {
    if (r.hour == null || r.hour < 0 || r.hour > 23) continue;
    byHour[r.hour].impressions += r.impressions;
    byHour[r.hour].revenue += r.revenue_yuan;
  }
  return byHour.map((b) => ({
    label: String(b.hour),
    tip: `${String(b.hour).padStart(2, '0')}:00　展示 ${b.impressions} 条 · 预估收益 ${b.revenue.toFixed(4)} 元`,
    impressions: b.impressions,
    revenue: b.revenue,
  }));
}

// 按天聚合,数据源是 daily(全量,不受 limit 影响);用 from..to 补齐空缺日为 0,轴连续
function aggregateDaily(dateFrom: string, dateTo: string, daily: AdRevenueDaily[]): TrendPoint[] {
  const map = new Map(daily.map((d) => [d.date, d]));
  const out: TrendPoint[] = [];
  let cur = dayjs(dateFrom);
  let guard = 0;
  while (cur.format('YYYY-MM-DD') <= dateTo && guard < 400) {
    const ds = cur.format('YYYY-MM-DD');
    const d = map.get(ds);
    const impressions = d?.impressions ?? 0;
    const revenue = d?.revenue_yuan ?? 0;
    out.push({
      label: ds.slice(5),
      tip: `${ds}　展示 ${impressions} 条 · 预估收益 ${revenue.toFixed(4)} 元`,
      impressions,
      revenue,
    });
    cur = cur.add(1, 'day');
    guard += 1;
  }
  return out;
}

function TrendChart({ points }: { points: TrendPoint[] }) {
  const n = points.length;
  const maxImp = Math.max(1, ...points.map((p) => p.impressions));
  const maxRev = Math.max(1e-9, ...points.map((p) => p.revenue));

  const W = 960;
  const H = 280;
  const padL = 48;
  const padR = 56;
  const padT = 16;
  const padB = 32;
  const plotW = W - padL - padR;
  const plotH = H - padT - padB;
  const step = plotW / Math.max(1, n);
  const barW = Math.min(28, step * 0.55);
  const yBase = padT + plotH;

  const barX = (i: number) => padL + i * step + (step - barW) / 2;
  const cx = (i: number) => padL + i * step + step / 2;
  const impH = (v: number) => (v / maxImp) * plotH;
  const revY = (v: number) => yBase - (v / maxRev) * plotH;
  const linePts = points.map((p, i) => `${cx(i)},${revY(p.revenue)}`).join(' ');
  const labelEvery = Math.max(1, Math.ceil(n / 12)); // 至多 ~12 个 x 标签,避免拥挤

  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: 'block' }} role="img" aria-label="趋势图">
      {[0, 0.25, 0.5, 0.75, 1].map((t) => {
        const y = yBase - t * plotH;
        return (
          <g key={t}>
            <line x1={padL} y1={y} x2={W - padR} y2={y} stroke="#f0f0f0" />
            <text x={padL - 8} y={y + 4} textAnchor="end" fontSize={11} fill={CHART_BAR}>
              {Math.round(maxImp * t)}
            </text>
            <text x={W - padR + 8} y={y + 4} textAnchor="start" fontSize={11} fill={CHART_LINE}>
              {(maxRev * t).toFixed(2)}
            </text>
          </g>
        );
      })}
      {points.map((p, i) => (
        <rect
          key={i}
          x={barX(i)}
          y={yBase - impH(p.impressions)}
          width={barW}
          height={impH(p.impressions)}
          fill={CHART_BAR}
          rx={2}
        >
          <title>{p.tip}</title>
        </rect>
      ))}
      <polyline points={linePts} fill="none" stroke={CHART_LINE} strokeWidth={2} />
      {points.map((p, i) => (
        <circle key={i} cx={cx(i)} cy={revY(p.revenue)} r={2.5} fill={CHART_LINE}>
          <title>{p.tip}</title>
        </circle>
      ))}
      {points.map((p, i) =>
        i % labelEvery === 0 || i === n - 1 ? (
          <text key={i} x={cx(i)} y={H - 14} textAnchor="middle" fontSize={11} fill="#999">
            {p.label}
          </text>
        ) : null,
      )}
    </svg>
  );
}

// 广告收益报表页:单表 —— 按 用户×类型×应用×代码位 聚合,一行同时给出
// 展示条数 / 收益 / 应发金币 / 实发金币 / 是否一致(发奖对账下沉到聚合级)。
// 穿山甲 app_id / 广告位 / 开关等配置在「广告配置」页(/ad-revenue)维护。
export default function AdRevenueReportPage() {
  const { message } = App.useApp();
  const [range, setRange] = useState<[Dayjs, Dayjs]>([dayjs(), dayjs()]);
  const [userId, setUserId] = useState<number | null>(null);
  const [adType, setAdType] = useState<string | undefined>();
  const [granularity, setGranularity] = useState<'day' | 'hour'>('day');
  const [limit, setLimit] = useState<number>(500);
  const [queriedGranularity, setQueriedGranularity] = useState<'day' | 'hour'>('day'); // 本次结果对应的粒度,决定是否显示「小时」列
  const [queriedMultiDay, setQueriedMultiDay] = useState(false); // 本次结果是否跨多天,决定显示「日期」列 + 按天/按小时图
  const [data, setData] = useState<AdRevenueReport | null>(null);
  const [queriedLimit, setQueriedLimit] = useState<number>(500);
  const [loading, setLoading] = useState(false);
  const [formulaOpen, setFormulaOpen] = useState(false);

  // 当前选择是否跨多天:跨多天时「按小时」无意义,粒度强制按天
  const rangeMultiDay = range[0].format('YYYY-MM-DD') !== range[1].format('YYYY-MM-DD');

  const load = useCallback(async () => {
    const from = range[0].format('YYYY-MM-DD');
    const to = range[1].format('YYYY-MM-DD');
    const multiDay = from !== to;
    const gran = multiDay ? 'day' : granularity; // 跨多天强制按天
    setLoading(true);
    try {
      const res = await api.get<AdRevenueReport>('/admin/api/ad-revenue-report', {
        params: {
          date_from: from,
          date_to: to,
          user_id: userId ?? undefined,
          ad_type: adType ?? undefined,
          granularity: gran,
          limit,
        },
      });
      setData(res.data);
      setQueriedLimit(limit);
      setQueriedGranularity(gran);
      setQueriedMultiDay(multiDay);
    } catch (e) {
      message.error(errMsg(e));
    } finally {
      setLoading(false);
    }
  }, [range, userId, adType, granularity, limit]);

  useEffect(() => {
    load();
    // 仅首次自动拉今天;之后由「查询」按钮触发
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const columns: ColumnsType<AdRevenueRow> = [
    ...(queriedMultiDay
      ? [{ title: '日期', dataIndex: 'report_date', width: 105, fixed: 'left' } as ColumnsType<AdRevenueRow>[number]]
      : []),
    { title: '时间', dataIndex: 'created_at', width: 165, render: (v: string) => formatUtcTime(v) },
    {
      title: '用户',
      dataIndex: 'user_phone',
      width: 150,
      render: (phone: string | null, r: AdRevenueRow) =>
        phone ? (
          <span>
            {phone}
            <Typography.Text type="secondary" style={{ fontSize: 11, marginLeft: 6 }}>
              #{r.user_id}
            </Typography.Text>
          </span>
        ) : (
          <Typography.Text type="secondary">#{r.user_id}（无手机号）</Typography.Text>
        ),
    },
    {
      title: '广告类型',
      dataIndex: 'ad_type',
      width: 110,
      render: (s: string) => {
        const t = TYPE_TAG[s] ?? { color: 'default', label: s };
        return <Tag color={t.color}>{t.label}</Tag>;
      },
    },
    {
      title: '来源应用',
      dataIndex: 'app_env',
      width: 140,
      render: (v: string | null) => {
        if (!v) return <Typography.Text type="secondary">未知</Typography.Text>;
        const t = APP_TAG[v] ?? { color: 'default', label: v };
        return <Tag color={t.color}>{t.label}</Tag>;
      },
    },
    {
      title: '广告位ID',
      dataIndex: 'our_code_id',
      width: 110,
      render: (v: string | null) =>
        v ? <Typography.Text code>{v}</Typography.Text> : <Typography.Text type="secondary">-</Typography.Text>,
    },
    {
      title: 'eCPM（分）',
      dataIndex: 'ecpm',
      width: 100,
      align: 'right',
      render: (v: string | null) => v ?? '-',
    },
    {
      title: '预估收益(元)',
      dataIndex: 'revenue_yuan',
      width: 110,
      align: 'right',
      render: (v: number, r: AdRevenueRow) => (r.has_impression ? v.toFixed(4) : '-'),
    },
    {
      title: '发奖状态',
      dataIndex: 'status',
      width: 100,
      render: (s: string | null) => {
        if (!s) return <Tag>仅展示</Tag>;
        const t = STATUS_TAG[s] ?? { color: 'default', label: s };
        return <Tag color={t.color}>{t.label}</Tag>;
      },
    },
    {
      title: '应发金币',
      dataIndex: 'expected_coin',
      width: 90,
      align: 'right',
      render: (v: number, r: AdRevenueRow) =>
        r.has_reward ? <b>{v}</b> : <Typography.Text type="secondary">-</Typography.Text>,
    },
    {
      title: '实发金币',
      dataIndex: 'actual_coin',
      width: 90,
      align: 'right',
      render: (v: number, r: AdRevenueRow) =>
        r.has_reward ? v : <Typography.Text type="secondary">-</Typography.Text>,
    },
    {
      title: '一致',
      dataIndex: 'matched',
      width: 70,
      align: 'center',
      render: (m: boolean, r: AdRevenueRow) =>
        r.has_reward ? (
          m ? <Tag color="green">✓</Tag> : <Tag color="red">✗ 不符</Tag>
        ) : (
          <Typography.Text type="secondary">-</Typography.Text>
        ),
    },
    {
      title: '底层 ADN',
      dataIndex: 'adn',
      width: 100,
      render: (v: string | null) =>
        v ? <Tag>{v}</Tag> : <Typography.Text type="secondary">-</Typography.Text>,
    },
  ];

  // 派生指标(全部基于全量 total_* 字段,不受 limit 截断影响,准):
  // 平均 eCPM = 收益÷展示×1000;发奖成本(元)= 实发金币÷汇率;预估毛利 = 收益−发奖成本;
  // 发奖占收益比 = 发奖成本÷收益;应发实发差额(金币)= 应发−实发(正=少发/负=多发)。
  const derived = data
    ? {
        avgEcpm: data.total_impressions
          ? (data.total_revenue_yuan / data.total_impressions) * 1000
          : 0,
        payoutYuan: data.total_actual_coin / COIN_PER_YUAN,
        grossProfit: data.total_revenue_yuan - data.total_actual_coin / COIN_PER_YUAN,
        payoutRatioPct:
          data.total_revenue_yuan > 0
            ? (data.total_actual_coin / COIN_PER_YUAN / data.total_revenue_yuan) * 100
            : null,
        coinGap: data.total_expected_coin - data.total_actual_coin,
      }
    : null;

  return (
    <div>
      <Space align="center" style={{ display: 'flex', marginBottom: 16 }}>
        <h2 style={{ margin: 0 }}>收益报表</h2>
        <Button
          type="link"
          size="small"
          icon={<QuestionCircleOutlined />}
          onClick={() => setFormulaOpen(true)}
        >
          金币计算规则
        </Button>
        <Popover
          placement="bottomLeft"
          title="收益口径说明"
          content={
            <div style={{ maxWidth: 360, fontSize: 13, lineHeight: 1.7 }}>
              「预估收益」为客户端在广告展示(onAdShow)时上报 eCPM 折算的预估值(每千次展示 ÷1000
              累加),<b>只要广告展示就计入、不论是否看完发奖</b>;穿山甲会过滤无效/过短曝光,故预估值可能偏高,
              <b>实际收益一律以穿山甲后台结算为准</b>。测试应用多为 0。「广告位ID / 来源应用」为本期新增,历史记录留空。
            </div>
          }
        >
          <Button type="link" size="small" icon={<InfoCircleOutlined />}>
            收益口径
          </Button>
        </Popover>
      </Space>

      <Card size="small" style={{ marginBottom: 16 }}>
        <Space wrap size="middle" align="center">
          <Space size={6}>
            <Typography.Text type="secondary">日期</Typography.Text>
            <RangePicker
              value={range}
              onChange={(v) => v && v[0] && v[1] && setRange([v[0], v[1]])}
              allowClear={false}
              presets={[
                { label: '今天', value: [dayjs(), dayjs()] },
                { label: '近 7 天', value: [dayjs().subtract(6, 'day'), dayjs()] },
                { label: '近 30 天', value: [dayjs().subtract(29, 'day'), dayjs()] },
              ]}
            />
          </Space>
          <Space size={6}>
            <Typography.Text type="secondary">用户</Typography.Text>
            <InputNumber
              placeholder="可空"
              value={userId ?? undefined}
              onChange={(v) => setUserId(v ?? null)}
              style={{ width: 130 }}
            />
          </Space>
          <Space size={6}>
            <Typography.Text type="secondary">类型</Typography.Text>
            <Select
              placeholder="全部"
              value={adType}
              onChange={setAdType}
              allowClear
              style={{ width: 150 }}
              options={[
                { value: 'reward_video', label: '激励视频' },
                { value: 'feed', label: '信息流' },
                { value: 'draw', label: 'Draw 信息流' },
                { value: 'withdrawal_video', label: '提现激励视频' },
              ]}
            />
          </Space>
          <Space size={6}>
            <Typography.Text type="secondary">粒度</Typography.Text>
            <Select
              value={rangeMultiDay ? 'day' : granularity}
              onChange={setGranularity}
              disabled={rangeMultiDay}
              style={{ width: 110 }}
              title={rangeMultiDay ? '跨多天仅支持按天' : undefined}
              options={[
                { value: 'day', label: '按天' },
                { value: 'hour', label: '按小时' },
              ]}
            />
          </Space>
          <Space size={6}>
            <Typography.Text type="secondary">条数</Typography.Text>
            <Select
              value={limit}
              onChange={setLimit}
              style={{ width: 110 }}
              options={[100, 200, 500, 1000].map((n) => ({ value: n, label: `${n} 条` }))}
            />
          </Space>
          <Button type="primary" onClick={load} loading={loading}>
            查询
          </Button>
        </Space>
      </Card>

      {data && derived && (
        <Card size="small" style={{ marginBottom: 16 }}>
          <Divider orientation="left" plain style={{ marginTop: 0, marginBottom: 16 }}>
            <Typography.Text type="secondary" style={{ fontSize: 12 }}>
              广告展示与收益
            </Typography.Text>
          </Divider>
          <Row gutter={[16, 12]}>
            <Col span={6}>
              <Statistic title="展示条数合计" value={data.total_impressions} />
            </Col>
            <Col span={6}>
              <Statistic title="平均 eCPM(元/千次)" value={derived.avgEcpm} precision={2} />
            </Col>
            <Col span={6}>
              <Statistic title="预估收益合计(元)" value={data.total_revenue_yuan} precision={4} />
            </Col>
            <Col span={6}>
              <Statistic title="广告事件数" value={data.total} />
            </Col>
          </Row>
          <Divider orientation="left" plain style={{ marginTop: 20, marginBottom: 16 }}>
            <Typography.Text type="secondary" style={{ fontSize: 12 }}>
              发奖与对账
            </Typography.Text>
          </Divider>
          <Row gutter={[16, 12]}>
            <Col span={6}>
              <Statistic title="应发金币合计" value={data.total_expected_coin} />
            </Col>
            <Col span={6}>
              <Statistic title="实发金币合计" value={data.total_actual_coin} />
            </Col>
            <Col span={6}>
              <Statistic title="发奖成本(元)" value={derived.payoutYuan} precision={4} />
            </Col>
            <Col span={6}>
              <Statistic
                title="预估毛利(元)"
                value={derived.grossProfit}
                precision={4}
                prefix={derived.grossProfit >= 0 ? <RiseOutlined /> : <FallOutlined />}
                valueStyle={{ color: derived.grossProfit >= 0 ? '#3f8600' : '#cf1322' }}
              />
              <Typography.Text type="secondary" style={{ fontSize: 12 }}>
                发奖占收益{' '}
                {derived.payoutRatioPct == null ? '—' : `${derived.payoutRatioPct.toFixed(0)}%`}
              </Typography.Text>
            </Col>
          </Row>
        </Card>
      )}

      {data && (
        <Space direction="vertical" style={{ width: '100%', marginBottom: 12 }}>
          {data.mismatch_count > 0 ? (
            <Alert
              type="error"
              showIcon
              message={`共 ${data.total} 条广告事件,其中 ${data.mismatch_count} 条应发≠实发(✗);应发−实发 合计 ${
                derived && derived.coinGap >= 0 ? '+' : ''
              }${derived?.coinGap ?? 0} 金币(正=少发/负=多发)——公式可能未生效或发奖有问题。定位到具体记录可用后端逐条审计接口。`}
            />
          ) : (
            <Alert type="success" showIcon message={`共 ${data.total} 条广告事件,应发与实发全部一致。`} />
          )}
          {data.truncated && (
            <Alert
              type="warning"
              showIcon
              message={`明细已截断到 ${queriedLimit} 条,还有更多未显示——请按用户/类型/日期缩小范围,或调大「条数」。合计数字不受影响。`}
            />
          )}
        </Space>
      )}

      {data &&
        (queriedMultiDay
          ? (data.daily?.length ?? 0) > 0
          : queriedGranularity === 'hour' && (data.items?.length ?? 0) > 0) && (
        <Card
          size="small"
          title={queriedMultiDay ? '按天趋势' : '按小时趋势'}
          style={{ marginBottom: 16 }}
          extra={
            <Space size={16}>
              <span style={{ fontSize: 12, color: '#666' }}>
                <span
                  style={{
                    display: 'inline-block',
                    width: 10,
                    height: 10,
                    background: CHART_BAR,
                    borderRadius: 2,
                    marginRight: 4,
                  }}
                />
                展示条数
              </span>
              <span style={{ fontSize: 12, color: '#666' }}>
                <span
                  style={{
                    display: 'inline-block',
                    width: 14,
                    borderTop: `2px solid ${CHART_LINE}`,
                    marginRight: 4,
                    verticalAlign: 'middle',
                  }}
                />
                预估收益(元)
              </span>
            </Space>
          }
        >
          {/* 按小时图由 items 聚合,会被 limit 截断;按天图用 daily(全量)不受影响,故只在按小时时警告 */}
          {!queriedMultiDay && data.truncated && (
            <Alert
              type="warning"
              showIcon
              style={{ marginBottom: 12 }}
              message={`图表按小时聚合的是已截断到 ${queriedLimit} 组的明细,可能少算——调大「条数」或缩小筛选可还原真实趋势。`}
            />
          )}
          {queriedMultiDay ? (
            <TrendChart points={aggregateDaily(data.date_from, data.date_to, data.daily)} />
          ) : (
            <TrendChart points={aggregateHourly(data.items)} />
          )}
        </Card>
      )}

      <Table
        rowKey="event_key"
        columns={columns}
        dataSource={data?.items ?? []}
        loading={loading}
        pagination={false}
        size="small"
        scroll={{ x: 1200 }}
        rowClassName={(r) => (r.has_reward && !r.matched ? 'row-mismatch' : '')}
        expandable={{
          // 只有发了奖的事件可展开,看「这条广告金币怎么算的」;纯展示行无复算、不可展开。
          rowExpandable: (r) => r.reward_detail != null,
          expandedRowRender: (r) =>
            r.reward_detail ? (
              <div>
                <Typography.Text strong>金币复算明细</Typography.Text>{' '}
                <Typography.Text type="secondary">
                  (本条广告 应发 {r.reward_detail.expected_coin} / 实发 {r.reward_detail.actual_coin})
                </Typography.Text>
                <Table<AdRevenueRecord>
                  style={{ marginTop: 8 }}
                  rowKey="record_id"
                  columns={DETAIL_COLUMNS}
                  dataSource={[r.reward_detail]}
                  pagination={false}
                  size="small"
                  scroll={{ x: 900 }}
                  rowClassName={(d) => (d.matched ? '' : 'row-mismatch')}
                />
              </div>
            ) : null,
        }}
      />
      <Modal
        title="金币计算规则"
        open={formulaOpen}
        onCancel={() => setFormulaOpen(false)}
        footer={null}
        width={680}
      >
        <Typography.Paragraph>
          <b>单次奖励(元)</b> ＝（单次 eCPM<sub>元</sub> ÷ 1000)× <b>因子1</b>(eCPM 档)× <b>因子2</b>(LT 累计条数)
        </Typography.Paragraph>
        <Typography.Paragraph type="secondary" style={{ fontSize: 13 }}>
          · eCPM 取自穿山甲 SDK <Typography.Text code>getEcpm()</Typography.Text> 原值,单位「分/千次展示」,计算时 ÷100 转元;单次收益按「每千次 ÷1000」折到每次。
          <br />· 金币汇率:<b>1 元 ＝ {COIN_PER_YUAN.toLocaleString()} 金币</b>,最终四舍五入取整。
          <br />· 因子2「LT 累计条数」按该账号累计第几条计(不按天重置);激励视频每次 1 条,信息流每满 10 秒折 1 条。
          <br />· 本表与发奖后端同源,本页「应发金币」即按此复算并与「实发金币」对账。
        </Typography.Paragraph>
        <Row gutter={16}>
          <Col span={12}>
            <Typography.Text strong>因子1 ·按 eCPM(元/千次)判档</Typography.Text>
            <Table
              style={{ marginTop: 8 }}
              size="small"
              pagination={false}
              dataSource={ECPM_FACTOR_ROWS}
              columns={[
                { title: 'eCPM 范围(元)', dataIndex: 'range' },
                { title: '因子1', dataIndex: 'factor', width: 80 },
              ]}
            />
          </Col>
          <Col span={12}>
            <Typography.Text strong>因子2 ·按账号 LT 累计条数</Typography.Text>
            <Table
              style={{ marginTop: 8 }}
              size="small"
              pagination={false}
              dataSource={LT_FACTOR_ROWS}
              columns={[
                { title: 'LT 累计条数', dataIndex: 'lt' },
                { title: '因子2', dataIndex: 'factor', width: 80 },
              ]}
            />
          </Col>
        </Row>
        <Typography.Paragraph type="secondary" style={{ fontSize: 13, marginTop: 12, marginBottom: 0 }}>
          示例:eCPM ＝ 300 元/千次、账号第 1 条 → 0.3 × 0.4 × 2.0 ＝ 0.24 元 ＝ 2,400 金币。
        </Typography.Paragraph>
      </Modal>

      <style jsx global>{`
        .row-mismatch > td {
          background: #fff1f0 !important;
        }
      `}</style>
    </div>
  );
}
