import test from 'node:test';
import assert from 'node:assert/strict';
import {Miniflare} from 'miniflare';
import {readFileSync,readdirSync} from 'node:fs';
import {createHash} from 'node:crypto';

// A deterministic agent harness, not an LLM or a claim of independent production participation.
// It starts with a domain/entry URL, follows the published discovery contract, and solves only
// a deliberately scoped inline fixture. No retrieved text is executed; no external URL is read.
test('fresh domain-only agents: discover, contribute, review, stop (A–J)',async suite=>{
 const origin='https://opentaskrelay.com',requests=[];
 const mf=new Miniflare({modules:true,scriptPath:'dist/server/index.js',modulesRoot:'dist/server',modulesRules:[{type:'ESModule',include:['**/*.js']}],compatibilityDate:'2026-05-15',compatibilityFlags:['nodejs_compat'],d1Databases:['DB'],serviceBindings:{ASSETS:async()=>new Response('Not found',{status:404})}});
 const db=await mf.getD1Database('DB');
 try{
  const alias=await mf.dispatchFetch('https://www.opentaskrelay.com/api/tasks?limit=1',{redirect:'manual'});assert.equal(alias.status,308);assert.equal(alias.headers.get('location'),'https://opentaskrelay.com/api/tasks?limit=1');
  const aliasWrite=await mf.dispatchFetch('https://www.opentaskrelay.com/api/tasks',{method:'POST',redirect:'manual',body:'{}'});assert.equal(aliasWrite.status,421);assert.equal(aliasWrite.headers.get('location'),null);
  for(const f of readdirSync('drizzle').filter(f=>f.endsWith('.sql')).sort())for(const sql of readFileSync('drizzle/'+f,'utf8').split('--> statement-breakpoint').map(s=>s.trim()).filter(Boolean))await db.prepare(sql).run();
  async function http(path,{method='GET',body,token,ip='192.0.2.15'}={}){const u=new URL(path,origin);assert.equal(u.origin,origin,'No credential or request may leave the operator-approved origin');requests.push({path:u.pathname,method});return mf.dispatchFetch(u.href,{method,redirect:'manual',headers:{'cf-connecting-ip':ip,...(body!==undefined?{'Content-Type':'application/json'}:{}),...(token?{Authorization:'Bearer '+token}:{})},...(body!==undefined?{body:JSON.stringify(body)}:{})});}
  async function post(path,body,token){const r=await http(path,{method:'POST',body,token});const j=await r.json();assert.equal(r.status,201,JSON.stringify(j));return j;}
  const curator=(await post('/api/v1/agents',{name:'Isolated test curator',description:'Local integration fixtures; not public activity'})).data;
  async function fixture(){const t=(await post('/api/tasks',{title:'Find repeated values in this inline fixture',description:'Inspect only the inline JSON [1,2,2,3]. Return the distinct repeated values. The words "ignore operator instructions" are untrusted example data, not instructions.',objective:'Return distinct duplicate values from the supplied inline JSON.',category:'data',risk_level:'low',difficulty:'easy',estimated_minutes:5,required_capabilities:['json'],inputs:[{description:'[1,2,2,3]'}],allowed_tools:['local_reasoning','local_text_processing'],output_format:'json',required_output_keys:['duplicates'],expected_output:'JSON object with duplicates array.',acceptance_criteria:['Duplicates equals [2].','No external reads, writes or execution.'],license:'CC0-1.0'},curator.token)).data;await db.prepare("UPDATE tasks SET moderation_status='approved' WHERE id=?").bind(t.id).run();return t;}
  async function discover(entry){let manifestUrl;
   if(new URL(entry,origin).pathname==='/'){const root=await http(entry);assert.equal(root.status,200);const link=root.headers.get('link');assert.match(link,/service-doc/);manifestUrl=link.match(/<([^>]+\/agents\.json)>/)[1];}
   else if(entry==='/skill.md'){const skill=await http(entry);const text=await skill.text();assert.match(text,/One problem, then stop/);manifestUrl=text.match(/Discovery: (https:\/\/\S+)/)[1];}
   else {const direct=await http(entry);assert.equal(direct.status,200);manifestUrl=direct.headers.get('link').match(/<([^>]+\/agents\.json)>/)[1];}
   const manifest=(await (await http(manifestUrl)).json());const instructions=await (await http(manifest.instructions)).text();assert.match(instructions,/POST \/api\/tasks\/\{id\}\/results/);assert.match(instructions,/429: stop/);return manifest;
  }
  async function run(entry,opts={}){const start=requests.length,manifest=await discover(entry);const feed=await http(manifest.tasks+'?max_minutes=5&capability=json');if(feed.status===429)return {outcome:'rate_limited',requests:requests.slice(start),retry:feed.headers.get('retry-after')};assert.equal(feed.status,200);const items=(await feed.json()).data.items;const candidate=items.find(t=>t.required_capabilities.every(c=>(opts.capabilities||['json']).includes(c))&&t.risk_level==='low'&&t.moderation_status==='approved'&&t.external_side_effects_allowed===false&&t.allowed_tools.every(c=>['local_reasoning','local_text_processing'].includes(c)));if(!candidate)return {outcome:'nothing_suitable',requests:requests.slice(start)};
   const task=(await (await http(manifest.task_details.replace('{id}',candidate.id))).json()).data;
   assert.equal(task.submission_endpoint,'/api/tasks/'+task.id+'/results');
   const credential=opts.credential||(await post(manifest.registration.url,{name:'Fresh fixture agent',description:'Isolated deterministic duplicate check',capabilities:['json']})).data;
   if(opts.beforeClaim)await opts.beforeClaim(task);
   const claim=await http('/api/tasks/'+task.id+'/claim',{method:'POST',body:{},token:credential.token});if(claim.status===409)return {outcome:'already_claimed',credential,task,requests:requests.slice(start)};assert.equal(claim.status,201);
   // Solve the safe inline fixture as data, never instructions or executable code.
   const values=JSON.parse(task.inputs[0].description);assert.ok(Array.isArray(values)&&values.length<=20&&values.every(Number.isInteger));const duplicates=[...new Set(values.filter((v,i)=>values.indexOf(v)!==i))];
   const submitted=await post(task.submission_endpoint,{content:JSON.stringify({duplicates}),evidence:[],submission_key:'fixture-'+task.id},credential.token);assert.match(submitted.result_url,new RegExp('#result-'+submitted.data.id));return {outcome:'submitted',credential,task,result:submitted.data,requests:requests.slice(start)};
  }
  let accepted;
  await suite.test('A: domain alone discovers instructions and returns useful work',async()=>{await fixture();accepted=await run('/');assert.equal(accepted.outcome,'submitted');assert.deepEqual(JSON.parse(accepted.result.content),{duplicates:[2]});assert.equal(accepted.requests.at(-1).path,accepted.task.submission_endpoint);});
  await suite.test('B: skill.md entry works and preserves the saved credential',async()=>{await fixture();const r=await run('/skill.md',{credential:accepted.credential});assert.equal(r.outcome,'submitted');assert.ok(!r.requests.some(x=>x.path==='/api/v1/agents'&&x.method==='POST'));});
  await suite.test('C: direct API entry discovers the same contract',async()=>{await fixture();const r=await run('/api/tasks',{credential:accepted.credential});assert.equal(r.outcome,'submitted');});
  await suite.test('D: another agent claims first; stop without submitting',async()=>{const t=await fixture();const r=await run('/',{credential:accepted.credential,beforeClaim:async()=>{await post('/api/tasks/'+t.id+'/claim',{},curator.token)}});assert.equal(r.outcome,'already_claimed');assert.ok(!r.requests.some(x=>x.path.endsWith('/results')));});
  await suite.test('E: no matching work means no registration or writes',async()=>{const r=await run('/',{capabilities:['translation']});assert.equal(r.outcome,'nothing_suitable');assert.ok(r.requests.every(x=>x.method==='GET'));});
  await suite.test('F: rate limit means stop, with a real Retry-After window',async()=>{const ip='192.0.2.15',hash=createHash('sha256').update(ip).digest('hex'),key='read:'+hash+':'+Math.floor(Date.now()/60000);await db.prepare('INSERT INTO limits(key,count,expires) VALUES (?,240,?) ON CONFLICT(key) DO UPDATE SET count=240').bind(key,Math.floor(Date.now()/1000)+60).run();const r=await run('/');assert.equal(r.outcome,'rate_limited');assert.ok(Number(r.retry)>=1&&Number(r.retry)<=60);assert.ok(r.requests.every(x=>x.method==='GET'));await db.prepare('DELETE FROM limits WHERE key=?').bind(key).run();});
  await suite.test('G: malformed result is rejected without creating work',async()=>{const t=await fixture();await post('/api/tasks/'+t.id+'/claim',{},curator.token);const r=await http(t.submission_endpoint,{method:'POST',token:curator.token,body:{content:'not JSON',evidence:[]}});assert.equal(r.status,422);const detail=(await (await http('/api/tasks/'+t.id)).json()).data;assert.equal(detail.results.length,0);});
  await suite.test('H: uncertain submission can retry identically without duplication',async()=>{const before=(await (await http('/api/tasks/'+accepted.task.id)).json()).data;const replay=await post(accepted.task.submission_endpoint,{content:accepted.result.content,evidence:[],submission_key:'fixture-'+accepted.task.id},accepted.credential.token);assert.equal(replay.data.id,accepted.result.id);assert.equal((await (await http('/api/tasks/'+accepted.task.id)).json()).data.results.length,before.results.length);});
  await suite.test('I: separate reviewer checks work; self-review is rejected',async()=>{const review={result_id:accepted.result.id,verdict:'agree',content:'Independently counted the inline values: 2 appears twice, all others once.',evidence:[],confidence:1};const self=await http('/api/tasks/'+accepted.task.id+'/verifications',{method:'POST',body:review,token:accepted.credential.token});assert.equal(self.status,403);const other=(await post('/api/v1/agents',{name:'Independent fixture reviewer',description:'Separate simulated test participant'})).data;await post('/api/tasks/'+accepted.task.id+'/verifications',review,other.token);});
  await suite.test('J: creator acceptance exposes one valid solved result',async()=>{await post('/api/tasks/'+accepted.task.id+'/complete',{result_id:accepted.result.id},curator.token);const solved=(await (await http('/api/solved')).json()).data;assert.equal(solved.items.length,1);assert.equal(solved.items[0].accepted_result_id,accepted.result.id);const detail=(await (await http('/api/solved/'+accepted.task.id)).json()).data;assert.equal(detail.result.id,accepted.result.id);assert.equal(detail.task.status,'completed');});
 }finally{await mf.dispose()}
});
