import { mkdir, writeFile, stat } from 'node:fs/promises';
import { join } from 'node:path';
import { expect } from 'e2e';
import { test, WEB } from './t3.ts';
import { T3_HOME } from '../e2e.config.ts';

const home = process.env.CODEX_HOME!;
const date = new Date().toISOString().slice(0, 10);
const usage = (input: number, output: number) => ({ input_tokens: input, cached_input_tokens: 0, cache_write_input_tokens: 0, output_tokens: output, reasoning_output_tokens: 0, total_tokens: input + output });
const line = (type: string, time: string, payload: unknown) => ({ type, timestamp: `${date}T${time}.000Z`, payload });
const count = (time: string, input: number, output: number, totalInput?: number, totalOutput?: number) => line('event_msg', time, { type: 'token_count', info: { last_token_usage: usage(input, output), ...(totalInput === undefined ? {} : { total_token_usage: usage(totalInput, totalOutput!) }) } });
const request = (time: string, input: number, output: number, response_id?: string) => line('token_usage_record', time, { usage: usage(input, output), ...(response_id ? { response_id } : {}) });

test('Codex rewind coverage and timestamp guards in Usage', WEB, async ({ t3, app, screen, browser }) => {
  await mkdir(join(home, 'sessions'), { recursive: true });
  await writeFile(join(home,'sessions','rollout-rewind-warning.jsonl'),'');
  const cases = {
    'rewind-warning': [count('10:00:00',100,20,100,20), request('10:00:05',40,8), request('10:00:06',20,4), count('10:00:07',20,4,60,12), count('10:00:08',10,2,70,14)],
    'last-only-guard': [count('10:00:00',100,20,100,20), count('10:00:05',20,4), count('10:00:10',30,6), count('10:00:05',20,4,120,24)],
    'rejected-baseline': [count('10:00:00',100,20,100,20), count('10:00:05',20,4,120,24), count('10:00:10',30,6,150,30), count('10:00:05',20,4,120,24), count('10:00:10',30,6,150,30)],
    'bridged-guard': [count('10:00:00',100,20,100,20), request('10:00:05',20,4), request('10:00:10',30,6,'response-bridge'), line('turn_context','10:00:11',{ model:'bridged-guard' }), count('10:00:05',20,4,120,24)],
  };
  const writeRollout = async (name: string, records: unknown[]) => {
    const meta = line('session_meta','09:59:00',{ id: `synthetic-${name}` });
    const context = line('turn_context','09:59:01',{ model:name });
    await writeFile(join(home,'sessions',`rollout-${name}.jsonl`), [meta,context,...records].map(record => JSON.stringify(record)).join('\n')+'\n');
  };
  for (const [name, records] of Object.entries(cases)) {
    if (name !== 'rewind-warning') await writeRollout(name, records);
  }
  // A synthetic v15 cache from the last-only prefix, followed by a grown rollout.
  const cachePath = join(home, 'sessions', 'rollout-cache-upgrade.jsonl');
  const prefix = [line('session_meta','09:59:00',{id:'synthetic-cache-upgrade'}),line('turn_context','09:59:01',{model:'cache-upgrade'}),...cases['last-only-guard'].slice(0,3)].map(record => JSON.stringify(record)).join('\n')+'\n';
  const buffer = Buffer.from(prefix);
  let hash = 0x811c9dc5;
  for (const byte of buffer.subarray(-64)) hash = Math.imul(hash ^ byte, 0x01000193);
  await writeFile(cachePath,prefix);
  const info = await stat(cachePath);
  const at = (time: string) => Date.parse(`${date}T${time}.000Z`);
  const cache = { version:15,models:['cache-upgrade'],sessions:['synthetic-cache-upgrade'],files:{[cachePath]:{s:buffer.length,m:info.mtimeMs,p:'codex',r:[[at('10:00:00'),0,0,100,0,0,20,0,null,null,0],[at('10:00:05'),0,0,20,0,0,4,0,null,null,0],[at('10:00:10'),0,0,30,0,0,6,0,null,null,0]],t:[],mr:0,o:buffer.length,gl:64,gh:hash>>>0,cs:{model:'cache-upgrade',speed:'standard',sessionId:'synthetic-cache-upgrade',lastUsageSignature:JSON.stringify(usage(30,6)),lastCumulativeUsage:usage(150,30),latestCumulativeAtMs:at('10:00:00'),malformedRecords:0,sawSessionMeta:true,modelProvider:'',turnId:'',sawThreadSettings:false,suppressingForkCopies:false,forkCopyAnchorMs:0}}}};
  await writeFile(join(T3_HOME,'userdata','usage-scan-cache-v15.json'),JSON.stringify(cache));
  await writeFile(cachePath,prefix+JSON.stringify(cases['last-only-guard'][3])+'\n');
  await t3.project('Usage proof');
  await t3.open('/usage');
  await screen.getByRole('button','Tokens', { exact: true }).tap();
  await screen.getByRole('button','Refresh usage').tap();
  await screen.getByRole('button','last-only-guard').waitFor({ state:'visible' });
  await screen.getByRole('button','Refresh usage',{disabled:false}).waitFor({state:'visible'});
  await app.screenshot('timestamp-and-cache-overview');
  await screen.getByRole('button','last-only-guard').scrollIntoView();
  await app.screenshot('timestamp-and-cache-totals');
  const skippedBefore = await browser.evaluate(() => Number(document.body.innerText.match(/(\d+) malformed or inconsistent usage/)?.[1] ?? 0));
  await writeRollout('rewind-warning', cases['rewind-warning']);
  await screen.getByRole('button','Refresh usage').tap();
  await screen.getByRole('button','rewind-warning').waitFor({state:'visible'});
  await screen.getByRole('button','Refresh usage',{disabled:false}).waitFor({state:'visible'});
  await screen.getByText(/malformed or inconsistent usage/).scrollIntoView();
  await app.screenshot('with-discarded-anonymous-request');
  const skippedAfter = await browser.evaluate(() => Number(document.body.innerText.match(/(\d+) malformed or inconsistent usage/)?.[1] ?? 0));
  await screen.getByRole('button','rewind-warning').scrollIntoView();
  await app.screenshot('all-request-totals');
  const rows = await browser.evaluate(() => Array.from(document.querySelectorAll('tr')).map(row => row.textContent ?? ''));
  const totals = Object.fromEntries([...Object.keys(cases),'cache-upgrade'].map(name => [name, rows.find(row => row.includes(name))?.match(/(\d+)\s*$/)?.[1]]));
  expect(totals).toEqual({ 'rewind-warning':'156', 'last-only-guard':'180', 'rejected-baseline':'180', 'bridged-guard':'180', 'cache-upgrade':'180' });
  expect(skippedAfter - skippedBefore).toBe(1);
});
