/** Shared verification for provider-specific Supabase Send SMS hooks. */
export type SmsOutcome={ok:true}|{ok:false;status:number;message:string};
const TOLERANCE=300;
const encoder=new TextEncoder();
export const failure=(status:number,message:string):SmsOutcome=>({ok:false,status,message});
export const unavailable=()=>failure(503,'ارسال کد پیامکی موقتاً در دسترس نیست. لطفاً بعداً تلاش کنید.');
function response(outcome:SmsOutcome){
 return Response.json(outcome.ok?{}:{error:{http_code:outcome.status,message:outcome.message}},
  {status:outcome.ok?200:outcome.status,headers:{'Cache-Control':'no-store','X-Content-Type-Options':'nosniff'}});
}
function base64(value:string){
 if(!/^[A-Za-z0-9+/]+={0,2}$/.test(value))throw new Error('Invalid encoding');
 return Uint8Array.from(atob(value),c=>c.charCodeAt(0));
}
async function signingKeys(secrets:string){
 const values=secrets.split('|').map(s=>s.trim());
 if(!values.length||values.length>3)throw new Error('Invalid signing configuration');
 return Promise.all(values.map(async value=>{
  if(!/^(v1,)?whsec_/.test(value))throw new Error('Invalid signing configuration');
  const bytes=base64(value.replace(/^(v1,)?whsec_/,''));
  if(bytes.length<24||bytes.length>64)throw new Error('Invalid signing configuration');
  return crypto.subtle.importKey('raw',bytes,{name:'HMAC',hash:'SHA-256'},false,['verify']);
 }));
}
/** Verify Standard Webhooks HMAC v1 over the unmodified body, ID and timestamp. */
export async function verifySmsSignature(body:string,headers:Headers,keys:CryptoKey[],now=Date.now()){
 const id=headers.get('webhook-id')||'',stamp=headers.get('webhook-timestamp')||'',signature=headers.get('webhook-signature')||'';
 if(!/^[a-zA-Z0-9_-]{1,128}$/.test(id)||!/^\d{1,12}$/.test(stamp)||signature.length>2048)return false;
 const timestamp=Number(stamp);
 if(!Number.isSafeInteger(timestamp)||Math.abs(Math.floor(now/1000)-timestamp)>TOLERANCE)return false;
 const signed=encoder.encode(`${id}.${stamp}.${body}`);
 for(const part of signature.split(' ').slice(0,10)){
  if(!part.startsWith('v1,'))continue;
  let bytes:Uint8Array;try{bytes=base64(part.slice(3))}catch{continue}
  if(bytes.length!==32)continue;
  for(const key of keys)if(await crypto.subtle.verify('HMAC',key,bytes as BufferSource,signed))return true;
 }
 return false;
}
export async function limitedText(body:ReadableStream<Uint8Array>|null,limit:number){
 const reader=body?.getReader();if(!reader)throw new Error('Missing body');
 let size=0,text='';const decoder=new TextDecoder('utf-8',{fatal:true});
 while(true){const {done,value}=await reader.read();if(done)break;size+=value.byteLength;if(size>limit){await reader.cancel();throw new Error('Body too large')}text+=decoder.decode(value,{stream:true})}
 return text+decoder.decode();
}
export type SmsHookConfig={
 hookSecrets:string;
 configured:boolean;
 normalizePhone:(value:unknown)=>string|null;
 send:(phone:string,otp:string)=>Promise<SmsOutcome>;
};
export function signedSmsHook(config:SmsHookConfig){
 const keys=signingKeys(config.hookSecrets).catch(()=>null);
 // Best-effort coalescing in one isolate; not a cross-instance exactly-once guarantee.
 const recent=new Map<string,{expires:number;fingerprint:string;outcome:Promise<SmsOutcome>}>();
 return async(request:Request):Promise<Response>=>{
  if(request.method!=='POST')return response(failure(405,'روش درخواست معتبر نیست.'));
  const verifiedKeys=await keys;
  if(!verifiedKeys||!config.configured)return response(unavailable());
  if(!request.headers.get('content-type')?.toLowerCase().startsWith('application/json'))return response(failure(415,'نوع درخواست معتبر نیست.'));
  let raw:string;try{raw=await limitedText(request.body,65536)}catch{return response(failure(413,'اندازه درخواست معتبر نیست.'))}
  if(!await verifySmsSignature(raw,request.headers,verifiedKeys))return response(failure(401,'امضای درخواست معتبر نیست.'));
  let event:{user?:{id?:unknown;phone?:unknown};sms?:{otp?:unknown}};
  try{event=JSON.parse(raw)}catch{return response(failure(400,'اطلاعات درخواست معتبر نیست.'))}
  const phone=config.normalizePhone(event?.user?.phone),otp=event?.sms?.otp;
  if(!phone||typeof event?.user?.id!=='string'||!event.user.id||typeof otp!=='string'||!/^\d{6}$/.test(otp))return response(failure(400,'اطلاعات پیامک معتبر نیست.'));
  const id=request.headers.get('webhook-id')!,now=Date.now();
  for(const [key,value] of recent)if(value.expires<now)recent.delete(key);
  const fingerprint=Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',encoder.encode(raw))),n=>n.toString(16).padStart(2,'0')).join('');
  const previous=recent.get(id);
  if(previous)return response(previous.fingerprint===fingerprint?await previous.outcome:failure(409,'شناسه درخواست تکراری است.'));
  if(recent.size>=2000)return response(failure(429,'سرویس ارسال پیامک شلوغ است. کمی بعد تلاش کنید.'));
  const outcome=config.send(phone,otp);recent.set(id,{fingerprint,expires:now+TOLERANCE*1000,outcome});
  return response(await outcome);
 };
}
