import type {Tokenomics} from './tokenization';

/** Read-only verifier. It never signs or broadcasts a transaction. */
export class IssuanceError extends Error {status:number;constructor(message:string,status=409){super(message);this.status=status}}
export type IssuanceConfig={rpcUrl:string;chainId:number;contractAddress:string;custodyAddress:string;minConfirmations:number};
export type IssuanceJob={request_id:string;proposal_version:number;tokenomics:Tokenomics;status:string;created_at:string;receipt?:Record<string,unknown>|null};
const address=/^0x[0-9a-f]{40}$/i,hash=/^0x[0-9a-f]{64}$/i;
const ZERO='0x'+'0'.repeat(40),ZERO_TOPIC='0x'+'0'.repeat(64);
const TRANSFER='0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
const fail=(message='رسید صدور با پیشنهاد پذیرفته‌شده مطابقت ندارد.'):never=>{throw new IssuanceError(message)};
export function issuanceConfig(raw:string,id:string):IssuanceConfig{
 let root:unknown;try{root=JSON.parse(raw)}catch{throw new IssuanceError('تنظیمات تأیید صدور هنوز آماده نیست.',503)}
 const c=root&&typeof root==='object'&&!Array.isArray(root)?(root as Record<string,unknown>)[id]:null;
 if(!c||typeof c!=='object'||Array.isArray(c))throw new IssuanceError('اتصال صدور این دارایی هنوز تنظیم نشده است.',503);
 const v=c as Record<string,unknown>;let url:URL;try{url=new URL(String(v.rpcUrl))}catch{throw new IssuanceError('تنظیمات اتصال صدور معتبر نیست.',503)}
 // The URL comes exclusively from deployment secrets; no URL is accepted from a caller.
 if(url.protocol!=='https:'||url.username||url.password||url.hash||url.hostname==='localhost'||url.hostname.endsWith('.localhost')||url.hostname.endsWith('.local')||/^[\d.]+$/.test(url.hostname)||url.hostname.includes(':')||!Number.isSafeInteger(v.chainId)||Number(v.chainId)<1||!Number.isSafeInteger(v.minConfirmations)||Number(v.minConfirmations)<12||Number(v.minConfirmations)>100000||typeof v.contractAddress!=='string'||!address.test(v.contractAddress)||v.contractAddress.toLowerCase()===ZERO||typeof v.custodyAddress!=='string'||!address.test(v.custodyAddress)||v.custodyAddress.toLowerCase()===ZERO)throw new IssuanceError('تنظیمات اتصال صدور معتبر نیست.',503);
 return {rpcUrl:url.toString(),chainId:Number(v.chainId),contractAddress:v.contractAddress.toLowerCase(),custodyAddress:v.custodyAddress.toLowerCase(),minConfirmations:Number(v.minConfirmations)};
}
export async function workerAuthorized(request:Request,secret:string){
 if(secret.length<32||request.headers.has('origin')||request.headers.get('sec-fetch-site')==='cross-site')return false;
 const received=request.headers.get('authorization')||'';if(received.length>512)return false;
 const encoder=new TextEncoder(),[a,b]=await Promise.all([crypto.subtle.digest('SHA-256',encoder.encode(received)),crypto.subtle.digest('SHA-256',encoder.encode('Bearer '+secret))]);
 const aa=new Uint8Array(a),bb=new Uint8Array(b);let difference=0;for(let i=0;i<aa.length;i++)difference|=aa[i]^bb[i];return difference===0;
}
async function boundedJson(response:Response){
 if(!response.ok||!response.body)throw new IssuanceError('ارتباط با سرویس تأیید صدور برقرار نشد.',503);
 const reader=response.body.getReader(),chunks:Uint8Array[]=[];let length=0;
 try{while(true){const {done,value}=await reader.read();if(done)break;length+=value.length;if(length>2*1024*1024){await reader.cancel();throw new IssuanceError('پاسخ سرویس تأیید صدور معتبر نیست.',503)}chunks.push(value)}}finally{reader.releaseLock()}
 const bytes=new Uint8Array(length);let offset=0;for(const part of chunks){bytes.set(part,offset);offset+=part.length}
 try{return JSON.parse(new TextDecoder().decode(bytes)) as Record<string,unknown>}catch{throw new IssuanceError('پاسخ سرویس تأیید صدور معتبر نیست.',503)}
}
function quantity(value:unknown){if(typeof value!=='string'||!/^0x(?:0|[1-9a-f][0-9a-f]{0,63})$/i.test(value))return fail();return BigInt(value)}
function word(value:unknown){if(typeof value!=='string'||!/^0x[0-9a-f]{64}$/i.test(value))return fail();return BigInt(value)}
function object(value:unknown):Record<string,unknown>{if(!value||typeof value!=='object'||Array.isArray(value))return fail();return value as Record<string,unknown>}
export function tokenAtoms(supply:string,decimals:number){
 if(!Number.isInteger(decimals)||decimals<0||decimals>18||! /^(?:0|[1-9][0-9]{0,19})(?:\.[0-9]{1,18})?$/.test(supply))return fail();
 const [whole,fraction='']=supply.split('.');if(fraction.length>decimals)return fail();const atoms=BigInt(whole)*BigInt(10)**BigInt(decimals)+BigInt(fraction.padEnd(decimals,'0')||'0');if(atoms<=BigInt(0))return fail();return atoms;
}
export async function verifyMintReceipt(job:IssuanceJob,config:IssuanceConfig,transactionHash:string,fetcher:typeof fetch=fetch){
 if(job.status!=='pending'||!hash.test(transactionHash)||job.tokenomics.wallet_destination!=='internal'||job.tokenomics.chain_id!==config.chainId)return fail();
 const txHash=transactionHash.toLowerCase(),atoms=tokenAtoms(job.tokenomics.total_supply,job.tokenomics.decimals);let sequence=0;
 const rpc=async(method:string,params:unknown[])=>{const id=++sequence;let response:Response;try{response=await fetcher(config.rpcUrl,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({jsonrpc:'2.0',id,method,params}),redirect:'error',cache:'no-store',signal:AbortSignal.timeout(12000)})}catch{throw new IssuanceError('ارتباط با شبکه صدور برقرار نشد.',503)}const data=await boundedJson(response);if(data.id!==id||data.jsonrpc!=='2.0'||data.error||!Object.hasOwn(data,'result'))throw new IssuanceError('پاسخ شبکه صدور معتبر نیست.',503);return data.result};
 if(quantity(await rpc('eth_chainId',[]))!==BigInt(config.chainId))return fail('شبکه رسید با شبکه پیشنهاد مطابقت ندارد.');
 const receipt=object(await rpc('eth_getTransactionReceipt',[txHash]));
 if(receipt.status!=='0x1'||String(receipt.transactionHash).toLowerCase()!==txHash||typeof receipt.blockHash!=='string'||!hash.test(receipt.blockHash)||!Array.isArray(receipt.logs))return fail('تراکنش صدور موفق و قابل تأیید نیست.');
 const blockNumber=quantity(receipt.blockNumber),head=quantity(await rpc('eth_blockNumber',[]));if(head<blockNumber)return fail();
 const confirmations=head-blockNumber+BigInt(1);if(confirmations<BigInt(config.minConfirmations))return fail('تراکنش هنوز تأییدهای کافی شبکه را دریافت نکرده است.');
 const blockTag='0x'+blockNumber.toString(16),blockHash=receipt.blockHash.toLowerCase();
 const [blockRaw,finalizedRaw]=await Promise.all([rpc('eth_getBlockByNumber',[blockTag,false]),rpc('eth_getBlockByNumber',['finalized',false])]);
 const block=object(blockRaw),finalized=object(finalizedRaw);
 if(String(block.hash).toLowerCase()!==blockHash||quantity(block.number)!==blockNumber||quantity(finalized.number)<blockNumber)return fail('بلاک صدور هنوز نهایی نشده یا تغییر کرده است.');
 const acceptedTime=Date.parse(job.created_at);if(!Number.isFinite(acceptedTime)||quantity(block.timestamp)*BigInt(1000)<BigInt(Math.ceil(acceptedTime/1000)*1000))return fail('رسید صدور باید پس از پذیرش پیشنهاد ایجاد شده باشد.');
 const topicRecipient='0x'+config.custodyAddress.slice(2).padStart(64,'0');
 const matches=receipt.logs.filter((raw:unknown)=>{const l=object(raw);return String(l.address).toLowerCase()===config.contractAddress&&Array.isArray(l.topics)&&l.topics.length===3&&String(l.topics[0]).toLowerCase()===TRANSFER&&String(l.topics[1]).toLowerCase()===ZERO_TOPIC&&String(l.topics[2]).toLowerCase()===topicRecipient});
 if(matches.length!==1)return fail('رویداد صدور یکتا به کیف پول امانی پلانک یافت نشد.');
 const log=object(matches[0]);if(log.removed===true||String(log.transactionHash).toLowerCase()!==txHash||String(log.blockHash).toLowerCase()!==blockHash||quantity(log.blockNumber)!==blockNumber||word(log.data)!==atoms)return fail();
 const logIndex=quantity(log.logIndex);if(logIndex>BigInt(999999999)||confirmations>BigInt(9999999999))return fail();
 const call=(data:string,at:string)=>rpc('eth_call',[{to:config.contractAddress,data},at]);
 const balanceSelector='0x70a08231'+config.custodyAddress.slice(2).padStart(64,'0');
 const [code,decimals,supply,balance,latestSupply,latestBalance]=await Promise.all([rpc('eth_getCode',[config.contractAddress,blockTag]),call('0x313ce567',blockTag),call('0x18160ddd',blockTag),call(balanceSelector,blockTag),call('0x18160ddd','latest'),call(balanceSelector,'latest')]);
 if(typeof code!=='string'||!/^0x(?:[0-9a-f]{2})+$/i.test(code)||word(decimals)!==BigInt(job.tokenomics.decimals)||word(supply)!==atoms||word(balance)<atoms||word(latestSupply)!==atoms||word(latestBalance)<atoms)return fail('قرارداد، عرضه یا موجودی امانی با پیشنهاد مطابقت ندارد.');
 const canonical=object(await rpc('eth_getBlockByNumber',[blockTag,false]));if(String(canonical.hash).toLowerCase()!==blockHash)return fail('بلاک صدور تغییر کرده است؛ بررسی دوباره لازم است.');
 return {verified_by:'rpc-mint-receipt-v1',transaction_hash:txHash,contract_address:config.contractAddress,recipient_address:config.custodyAddress,chain_id:config.chainId,amount:job.tokenomics.total_supply,decimals:job.tokenomics.decimals,block_hash:blockHash,block_number:blockNumber.toString(),log_index:Number(logIndex),confirmations:Number(confirmations)};
}
