export const walletAddress = (value: unknown): value is `0x${string}` => typeof value === 'string' && /^0x[0-9a-fA-F]{40}$/.test(value) && !/^0x0{40}$/.test(value);
export const walletChain = (value: unknown): value is number => typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
const uuid = (value: unknown): value is string => typeof value === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
export function walletMessage(origin: string, address: string, chain: number, userId: string, id: string, issued: string, expires: string) {
 const url = new URL(origin);
 return `${url.host} wants you to sign in with your Ethereum account:\n${address}\n\nLink this wallet to your Planck marketplace account. No transaction or token approval.\n\nURI: ${origin}/account/banks\nVersion: 1\nChain ID: ${chain}\nNonce: ${id.replaceAll('-','')}\nIssued At: ${issued}\nExpiration Time: ${expires}\nRequest ID: ${id}\nResources:\n- urn:planck:account:${userId}`;
}
type Config = {url:string;key:string;allowedOrigins:string[];recover:(message:string,signature:`0x${string}`)=>Promise<string>;checksum:(address:`0x${string}`)=>string;fetcher?:typeof fetch};
class WalletError extends Error {status:number;constructor(code:string,status=400){super(code);this.status=status}}
export function walletLinkHandler(config:Config) {
 const fetcher=config.fetcher||fetch;
 const reply=(body:unknown,status=200)=>Response.json(body,{status,headers:{'Cache-Control':'no-store','X-Content-Type-Options':'nosniff'}});
 async function rpc<T=Record<string,unknown>>(name:string,body:unknown):Promise<T>{
  const r=await fetcher(`${config.url}/rest/v1/rpc/${name}`,{method:'POST',headers:{apikey:config.key,Authorization:`Bearer ${config.key}`,'Content-Type':'application/json'},body:JSON.stringify(body),signal:AbortSignal.timeout(10000)});
  const data=await r.json() as Record<string,unknown>;
  if(!r.ok){const code=String(data?.message||'');if(['wallet_rate_limit','wallet_challenge_expired','wallet_already_linked','wallet_limit'].includes(code))throw new WalletError(code,code==='wallet_rate_limit'?429:409);throw new WalletError('wallet_unavailable',503)}
  return data as T;
 }
 return async(request:Request)=>{
  try{
   if(request.method!=='POST')return reply({error:'method_not_allowed'},405);
   if(!config.url||!config.key)throw new WalletError('wallet_unavailable',503);
   const bearer=request.headers.get('authorization');if(!bearer?.startsWith('Bearer '))throw new WalletError('unauthorized',401);
   if(!request.headers.get('content-type')?.startsWith('application/json'))throw new WalletError('invalid_request',415);
   const reader=request.body?.getReader();if(!reader)throw new WalletError('invalid_request');
   let size=0;const chunks:Uint8Array[]=[];while(true){const {done,value}=await reader.read();if(done)break;size+=value.length;if(size>8000){await reader.cancel();throw new WalletError('invalid_request',413)}chunks.push(value)}
   const bytes=new Uint8Array(size);let offset=0;for(const chunk of chunks){bytes.set(chunk,offset);offset+=chunk.length}
   let body;try{body=JSON.parse(new TextDecoder().decode(bytes))}catch{throw new WalletError('invalid_request')}
   if(!body||typeof body!=='object'||!config.allowedOrigins.includes(body.origin))throw new WalletError('origin_not_allowed',403);
   const auth=await fetcher(`${config.url}/auth/v1/user`,{headers:{apikey:config.key,Authorization:bearer},signal:AbortSignal.timeout(8000)});
   const user=auth.ok?await auth.json() as {id?:unknown}:null;if(!uuid(user?.id))throw new WalletError('unauthorized',401);
   if(body.action==='challenge'){
    if(!walletAddress(body.address)||!walletChain(body.chainId))throw new WalletError('invalid_wallet');
    const id=crypto.randomUUID(),issued=new Date().toISOString(),expires=new Date(Date.now()+300000).toISOString(),address=config.checksum(body.address);
    const label=typeof body.label==='string'?body.label.replace(/[\u0000-\u001f\u007f]/g,'').slice(0,60):'Public wallet';
    const message=walletMessage(body.origin,address,body.chainId,user.id,id,issued,expires);
    return reply(await rpc('marketplace_wallet_challenge',{p_user:user.id,p_id:id,p_address:address,p_chain:body.chainId,p_origin:body.origin,p_message:message,p_label:label,p_expires:expires}));
   }
   if(body.action==='verify'){
    if(!uuid(body.id)||typeof body.signature!=='string'||!/^0x[0-9a-fA-F]{130}$/.test(body.signature))throw new WalletError('invalid_signature');
    const args={p_user:user.id,p_id:body.id,p_origin:body.origin};
    const challenge=await rpc<{message:string;address:string;chain_id:number}>('marketplace_wallet_attempt',args);
    let signer;try{signer=await config.recover(challenge.message,body.signature)}catch{throw new WalletError('invalid_signature')}
    if(!walletAddress(signer)||signer.toLowerCase()!==challenge.address.toLowerCase())throw new WalletError('invalid_signature');
    return reply({wallet:await rpc('marketplace_wallet_complete',args)});
   }
   throw new WalletError('invalid_request');
  }catch(error){return error instanceof WalletError?reply({error:error.message},error.status):reply({error:'wallet_unavailable'},503)}
 };
}
