/* global React, ReactDOM, SAMPLE_DATA */
const { useState, useEffect, useRef, useCallback } = React;
const D = window.SAMPLE_DATA;

const LS_KEY = 'openapi-sample-creds';

function loadCreds() {
  try {
    return JSON.parse(localStorage.getItem(LS_KEY) || '{}');
  } catch (e) {
    return {};
  }
}
function saveCreds(creds) {
  try {
    localStorage.setItem(LS_KEY, JSON.stringify(creds));
  } catch (e) { /* 忽略 */ }
}

function pretty(obj) {
  return typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2);
}

async function copyText(text) {
  try {
    await navigator.clipboard.writeText(text);
    return true;
  } catch (e) {
    return false;
  }
}

/** 结果面板：显示上游 HTTP 状态、耗时与 JSON */
function Result({ data }) {
  if (!data) return null;
  const okBiz = data.response && data.response.code === 200;
  return (
    <div className="result">
      <div className="bar">
        {data.error ? (
          <span className="err">代理错误</span>
        ) : (
          <span className={okBiz ? 'ok' : 'err'}>
            HTTP {data.httpStatus} · code {data.response && data.response.code}
          </span>
        )}
        <span>{data.durationMs} ms</span>
        <span style={{ flex: 1 }} />
        <button className="ghost" onClick={() => copyText(pretty(data.response || data.error))}>复制响应</button>
        {data.request && <button className="ghost" onClick={() => copyText(data.request.curl)}>复制 curl</button>}
      </div>
      <pre>{pretty(data.error || data.response)}</pre>
    </div>
  );
}

/** 凭据与目标地址 */
function Credentials({ creds, setCreds, config, api, taskType }) {
  const [showKey, setShowKey] = useState(false);
  const [balance, setBalance] = useState(null);
  const [product, setProduct] = useState(null);
  const [busy, setBusy] = useState('');

  const update = (patch) => setCreds({ ...creds, ...patch });

  const queryBalance = async () => {
    setBusy('balance');
    setBalance(await api('GET', '/proxy/balance'));
    setBusy('');
  };
  const queryProduct = async () => {
    if (!taskType) return;
    setBusy('product');
    setProduct(await api('GET', `/proxy/product/${encodeURIComponent(taskType)}`));
    setBusy('');
  };

  return (
    <section className="card">
      <h2><span><span className="step">1</span>凭据与地址</span></h2>
      <div className="field">
        <label>API 地址（不含 /api/openapi）</label>
        <input type="text" value={creds.apiBase || ''} placeholder={config.apiBaseUrl}
          onChange={(e) => update({ apiBase: e.target.value })} />
        <span className="hint">留空则使用代理服务配置的默认地址：{config.apiBaseUrl}</span>
      </div>
      <div className="row">
        <div className="field">
          <label>X-Api-Account（账号）</label>
          <input type="text" value={creds.account || ''} autoComplete="off"
            onChange={(e) => update({ account: e.target.value })} />
        </div>
        <div className="field">
          <label>ApiKey（用于签名，不直接发送）</label>
          <div style={{ display: 'flex', gap: 6 }}>
            <input type={showKey ? 'text' : 'password'} value={creds.apiKey || ''} autoComplete="off"
              style={{ flex: 1 }} onChange={(e) => update({ apiKey: e.target.value })} />
            <button className="ghost" type="button" onClick={() => setShowKey(!showKey)}>{showKey ? '隐藏' : '显示'}</button>
          </div>
          <span className="hint">签名规则：X-Api-Key = 时间戳.Base64(HmacSHA256(账号:时间戳, ApiKey))，3 分钟内有效</span>
        </div>
      </div>
      <div className="btns">
        <button className="secondary" disabled={!!busy} onClick={queryBalance}>{busy === 'balance' ? '查询中…' : '查询余额'}</button>
        <button className="secondary" disabled={!!busy || !taskType} onClick={queryProduct}>
          {busy === 'product' ? '查询中…' : `查询产品状态${taskType ? `（${taskType}）` : ''}`}
        </button>
      </div>
      <Result data={balance} />
      <Result data={product} />
    </section>
  );
}

/** 任务类型下拉 + 可手输 */
function TaskTypeSelect({ value, onChange }) {
  const known = D.taskTypes.some((g) => g.items.some(([code]) => code === value));
  const [custom, setCustom] = useState(!known && !!value);
  return (
    <div className="field">
      <label>任务类型 taskType</label>
      {custom ? (
        <div style={{ display: 'flex', gap: 6 }}>
          <input type="text" value={value} style={{ flex: 1 }} placeholder="手工输入 taskType"
            onChange={(e) => onChange(e.target.value.trim())} />
          <button className="ghost" type="button" onClick={() => { setCustom(false); }}>选列表</button>
        </div>
      ) : (
        <div style={{ display: 'flex', gap: 6 }}>
          <select value={value} style={{ flex: 1 }} onChange={(e) => onChange(e.target.value)}>
            <option value="">请选择</option>
            {D.taskTypes.map((g) => (
              <optgroup key={g.group} label={g.group}>
                {g.items.map(([code, name]) => <option key={code} value={code}>{name}（{code}）</option>)}
              </optgroup>
            ))}
          </select>
          <button className="ghost" type="button" onClick={() => setCustom(true)}>手输</button>
        </div>
      )}
    </div>
  );
}

/** 国家选择：下拉或手输，区号跟随下拉自动填充、可改 */
function CountrySelect({ countryCode, areaCode, onChange }) {
  const matched = D.countries.find(([code]) => code === countryCode);
  const [custom, setCustom] = useState(!matched && !!countryCode);

  const pick = (code) => {
    const c = D.countries.find(([x]) => x === code);
    onChange({ countryCode: code, countryAreaCode: c ? c[2] : '' });
  };

  return (
    <div className="row">
      <div className="field">
        <label>国家代码 countryCode</label>
        {custom ? (
          <div style={{ display: 'flex', gap: 6 }}>
            <input type="text" value={countryCode} style={{ flex: 1 }} placeholder="如 US"
              onChange={(e) => onChange({ countryCode: e.target.value.trim().toUpperCase() })} />
            <button className="ghost" type="button" onClick={() => setCustom(false)}>选列表</button>
          </div>
        ) : (
          <div style={{ display: 'flex', gap: 6 }}>
            <select value={matched ? countryCode : ''} style={{ flex: 1 }} onChange={(e) => pick(e.target.value)}>
              <option value="">请选择</option>
              {D.countries.map(([code, name, dial]) => (
                <option key={code} value={code}>{name} {code}（+{dial}）</option>
              ))}
            </select>
            <button className="ghost" type="button" onClick={() => setCustom(true)}>手输</button>
          </div>
        )}
      </div>
      <div className="field">
        <label>国家区号 countryAreaCode</label>
        <input type="text" value={areaCode} placeholder="如 1，不带 +"
          onChange={(e) => onChange({ countryAreaCode: e.target.value.replace(/[^0-9]/g, '') })} />
        <span className="hint">手机号类任务必填；勾选「跳过国家过滤」后可留空</span>
      </div>
    </div>
  );
}

/** 上传并创建任务 */
function CreateTask({ api, taskType, setTaskType, onCreated }) {
  const [file, setFile] = useState(null);
  const [description, setDescription] = useState('');
  const [country, setCountry] = useState({ countryCode: 'US', countryAreaCode: '1' });
  const [skipDedup, setSkipDedup] = useState(false);
  const [skipCountryFilter, setSkipCountryFilter] = useState(false);
  const [batch, setBatch] = useState(false);
  const [busy, setBusy] = useState(false);
  const [result, setResult] = useState(null);
  const fileRef = useRef(null);

  const skipAllowed = D.skipFlagTaskTypes.includes(taskType);

  const submit = async () => {
    if (!file) { alert('请选择 .txt 文件'); return; }
    if (!taskType) { alert('请选择任务类型'); return; }
    const fd = new FormData();
    fd.append('file', file);
    fd.append('taskType', taskType);
    fd.append('description', description || file.name);
    fd.append('countryCode', country.countryCode);
    fd.append('countryAreaCode', country.countryAreaCode);
    fd.append('skipDedup', String(skipDedup));
    fd.append('skipCountryFilter', String(skipCountryFilter));
    fd.append('batch', String(batch));
    setBusy(true);
    const r = await api('POST', '/proxy/upload', { formData: fd });
    setBusy(false);
    setResult(r);
    const data = r.response && r.response.data;
    if (r.response && r.response.code === 200 && data) {
      const ids = typeof data === 'string' ? [data] : (data.taskIds || []);
      if (ids.length) onCreated(ids, taskType);
    }
  };

  return (
    <section className="card">
      <h2><span><span className="step">2</span>上传并创建任务</span></h2>
      <TaskTypeSelect value={taskType} onChange={setTaskType} />
      <CountrySelect countryCode={country.countryCode} areaCode={country.countryAreaCode}
        onChange={(patch) => setCountry({ ...country, ...patch })} />
      <div className="row">
        <div className="field">
          <label>文件（仅 .txt，每行一条）</label>
          <input type="file" accept=".txt" ref={fileRef}
            onChange={(e) => { const f = e.target.files[0] || null; setFile(f); if (f && !description) setDescription(f.name); }} />
        </div>
        <div className="field">
          <label>描述 description</label>
          <input type="text" value={description} placeholder="默认取文件名" onChange={(e) => setDescription(e.target.value)} />
        </div>
      </div>

      <div className="check">
        <input type="checkbox" id="batch" checked={batch} onChange={(e) => setBatch(e.target.checked)} />
        <div>
          <label htmlFor="batch">使用批量接口 uploadAndCreateTasks（超量自动拆分，返回任务 ID 数组）</label>
          <div className="desc">不勾选则调用 uploadAndCreateTask，单任务、返回单个任务 ID</div>
        </div>
      </div>
      <div className="check">
        <input type="checkbox" id="skipDedup" checked={skipDedup} onChange={(e) => setSkipDedup(e.target.checked)} />
        <div>
          <label htmlFor="skipDedup">skipDedup 跳过去重</label>
          <div className="desc">文件中重复号码全部保留，重复项同样计费</div>
        </div>
      </div>
      <div className="check">
        <input type="checkbox" id="skipCountryFilter" checked={skipCountryFilter} onChange={(e) => setSkipCountryFilter(e.target.checked)} />
        <div>
          <label htmlFor="skipCountryFilter">skipCountryFilter 跳过国家过滤</label>
          <div className="desc">不按国家区号过滤，也不做 US/CA/DO 区号白名单校验；countryCode 仍必传</div>
        </div>
      </div>
      {(skipDedup || skipCountryFilter) && !skipAllowed && (
        <p className="inline-note" style={{ color: 'var(--warn)' }}>
          当前任务类型不在跳过参数白名单内（当前白名单：{D.skipFlagTaskTypes.join(', ')}），服务端会拒绝该请求。可用于验证拒绝逻辑。
        </p>
      )}

      <div className="btns">
        <button disabled={busy} onClick={submit}>{busy ? '上传中…' : '上传并创建任务'}</button>
        <button className="secondary" type="button" onClick={() => { setFile(null); setDescription(''); setResult(null); if (fileRef.current) fileRef.current.value = ''; }}>清空</button>
      </div>
      <Result data={result} />
    </section>
  );
}

/** 任务状态查询与轮询 */
function TaskStatus({ api, taskId, setTaskId, taskIds }) {
  const [result, setResult] = useState(null);
  const [polling, setPolling] = useState(false);
  const timer = useRef(null);

  const query = useCallback(async () => {
    if (!taskId) return null;
    const r = await api('GET', `/proxy/task/${encodeURIComponent(taskId)}`);
    setResult(r);
    return r;
  }, [api, taskId]);

  useEffect(() => {
    if (!polling) { clearInterval(timer.current); return undefined; }
    query();
    timer.current = setInterval(async () => {
      const r = await query();
      const st = r && r.response && r.response.data && r.response.data.taskStatus;
      if (st === 1 || st === -1 || st === 3) setPolling(false);
    }, 5000);
    return () => clearInterval(timer.current);
  }, [polling, query]);

  const data = result && result.response && result.response.code === 200 ? result.response.data : null;

  return (
    <section className="card">
      <h2><span><span className="step">3</span>查询任务状态</span></h2>
      <div className="field">
        <label>任务 ID</label>
        <div style={{ display: 'flex', gap: 6 }}>
          <input type="text" value={taskId} style={{ flex: 1 }} placeholder="创建成功后自动填入"
            onChange={(e) => setTaskId(e.target.value.trim())} />
          {taskIds.length > 1 && (
            <select value={taskId} onChange={(e) => setTaskId(e.target.value)} style={{ maxWidth: 260 }}>
              {taskIds.map((id) => <option key={id} value={id}>{id}</option>)}
            </select>
          )}
        </div>
      </div>
      <div className="btns">
        <button disabled={!taskId} onClick={query}>查询一次</button>
        <button className="secondary" disabled={!taskId} onClick={() => setPolling(!polling)}>
          {polling ? '停止轮询' : '每 5 秒轮询直到完成'}
        </button>
      </div>
      {data && (
        <div style={{ marginTop: 12 }}>
          <table className="kv">
            <tbody>
              <tr><th>状态</th><td>
                <span className={`status-pill s${data.taskStatus}`}>{D.taskStatusText[String(data.taskStatus)] || data.taskStatus}</span>
                <div className="progress"><div style={{ width: `${data.taskProgress || 0}%` }} /></div>
              </td></tr>
              <tr><th>任务类型</th><td>{data.taskType}</td></tr>
              <tr><th>国家</th><td>{data.countryCode}</td></tr>
              <tr><th>提交 / 有效</th><td>{data.taskNumber} / {data.effectiveQuantity}</td></tr>
              <tr><th>单价 / 扣费</th><td>{data.price} / {data.accountExpend}</td></tr>
              <tr><th>创建时间</th><td>{data.createTime}</td></tr>
            </tbody>
          </table>
        </div>
      )}
      <Result data={result} />
    </section>
  );
}

/** 生成下载链接 */
function Download({ api, taskId, taskType }) {
  const [downloadType, setDownloadType] = useState('txt');
  const [filters, setFilters] = useState({ activeDay: '', startAge: '', endAge: '', sexStr: '' });
  const [localTaskType, setLocalTaskType] = useState(taskType);
  const [result, setResult] = useState(null);
  const [busy, setBusy] = useState(false);

  useEffect(() => { setLocalTaskType(taskType); }, [taskType]);

  const submit = async () => {
    setBusy(true);
    const body = { taskId, taskType: localTaskType, downloadType, ...filters };
    setResult(await api('POST', '/proxy/download', { json: body }));
    setBusy(false);
  };
  const url = result && result.response && result.response.code === 200 ? result.response.data : null;

  return (
    <section className="card">
      <h2><span><span className="step">4</span>生成下载链接</span></h2>
      <p className="inline-note">任务状态为「完成」后可用。链接有效期 30 天。</p>
      <div className="row">
        <div className="field">
          <label>任务类型（与上传一致）</label>
          <input type="text" value={localTaskType} onChange={(e) => setLocalTaskType(e.target.value.trim())} />
        </div>
        <div className="field">
          <label>格式</label>
          <select value={downloadType} onChange={(e) => setDownloadType(e.target.value)}>
            <option value="txt">txt</option>
            <option value="excel">excel</option>
          </select>
        </div>
      </div>
      <div className="row">
        <div className="field"><label>activeDay</label><input type="number" value={filters.activeDay} onChange={(e) => setFilters({ ...filters, activeDay: e.target.value })} /></div>
        <div className="field"><label>startAge</label><input type="number" value={filters.startAge} onChange={(e) => setFilters({ ...filters, startAge: e.target.value })} /></div>
        <div className="field"><label>endAge</label><input type="number" value={filters.endAge} onChange={(e) => setFilters({ ...filters, endAge: e.target.value })} /></div>
        <div className="field"><label>sexStr</label><input type="text" value={filters.sexStr} placeholder="1 / 0 / 1,0" onChange={(e) => setFilters({ ...filters, sexStr: e.target.value })} /></div>
      </div>
      <div className="btns">
        <button disabled={busy || !taskId || !localTaskType} onClick={submit}>{busy ? '生成中…' : '生成下载链接'}</button>
      </div>
      {url && <div className="link-box"><a href={url} target="_blank" rel="noreferrer">{url}</a></div>}
      <Result data={result} />
    </section>
  );
}

/** 请求日志：每次调用的请求快照、curl 与响应 */
function RequestLog({ logs, clear }) {
  return (
    <section className="card span-2">
      <h2>
        <span>请求日志（最近 {logs.length} 条）</span>
        <button className="ghost" onClick={clear}>清空</button>
      </h2>
      <div className="log-list">
        {logs.map((item) => (
          <details className="log-item" key={item.id}>
            <summary>
              <span className="m">{item.request ? item.request.method : '-'}</span>
              <span className="u">{item.request ? item.request.url : item.path}</span>
              <span className={item.response && item.response.code === 200 ? 'ok' : 'err'}>
                {item.error ? '代理错误' : `HTTP ${item.httpStatus} · code ${item.response && item.response.code}`}
              </span>
              <span>{item.durationMs} ms</span>
              <span>{item.at}</span>
            </summary>
            <div className="body">
              {item.request && (
                <>
                  <h4>请求头</h4>
                  <pre>{pretty(item.request.headers)}</pre>
                  {item.request.form && (<><h4>表单</h4><pre>{pretty(item.request.form)}</pre></>)}
                  {item.request.json && (<><h4>JSON</h4><pre>{pretty(item.request.json)}</pre></>)}
                  <h4>curl <button className="ghost" onClick={() => copyText(item.request.curl)}>复制</button></h4>
                  <pre>{item.request.curl}</pre>
                </>
              )}
              <h4>响应</h4>
              <pre>{pretty(item.error || item.response)}</pre>
            </div>
          </details>
        ))}
        {!logs.length && <p className="inline-note">还没有请求。</p>}
      </div>
    </section>
  );
}

function App() {
  const [config, setConfig] = useState({ apiBaseUrl: '', brandName: '' });
  const [creds, setCredsState] = useState(loadCreds);
  const [taskType, setTaskType] = useState('tgAdvanced');
  const [taskIds, setTaskIds] = useState([]);
  const [taskId, setTaskId] = useState('');
  const [logs, setLogs] = useState([]);

  useEffect(() => {
    fetch('/config').then((r) => r.json()).then(setConfig).catch(() => {});
  }, []);

  const setCreds = (c) => { setCredsState(c); saveCreds(c); };

  /** 统一调用本地代理：附带凭据头，记录日志 */
  const api = useCallback(async (method, path, { formData, json } = {}) => {
    const headers = {
      'x-sample-account': creds.account || '',
      'x-sample-apikey': creds.apiKey || '',
    };
    if (creds.apiBase) headers['x-sample-apibase'] = creds.apiBase;
    if (json) headers['Content-Type'] = 'application/json';
    let payload;
    try {
      const resp = await fetch(path, { method, headers, body: formData || (json ? JSON.stringify(json) : undefined) });
      payload = await resp.json();
      if (!resp.ok && payload && payload.error && !payload.request) {
        payload = { error: payload.error, httpStatus: resp.status, durationMs: 0, request: null };
      }
    } catch (e) {
      payload = { error: `调用本地代理失败：${e.message}`, httpStatus: 0, durationMs: 0, request: null };
    }
    setLogs((prev) => [{ id: Date.now() + Math.random(), at: new Date().toLocaleTimeString(), path, ...payload }, ...prev].slice(0, 50));
    return payload;
  }, [creds]);

  const onCreated = (ids, type) => {
    setTaskIds(ids);
    setTaskId(ids[0]);
    setTaskType(type);
  };

  const title = config.brandName ? `${config.brandName} OpenAPI 调试台` : 'OpenAPI 调试台';

  return (
    <>
      <header>
        <h1>{title}</h1>
        <span className="base">上游：{creds.apiBase || config.apiBaseUrl}</span>
      </header>
      <main>
        <Credentials creds={creds} setCreds={setCreds} config={config} api={api} taskType={taskType} />
        <CreateTask api={api} taskType={taskType} setTaskType={setTaskType} onCreated={onCreated} />
        <TaskStatus api={api} taskId={taskId} setTaskId={setTaskId} taskIds={taskIds} />
        <Download api={api} taskId={taskId} taskType={taskType} />
        <RequestLog logs={logs} clear={() => setLogs([])} />
      </main>
    </>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
