export const exchangeSources=['wallex','nobitex'] as const;
export type ExchangeSource=typeof exchangeSources[number];
export type ExchangePolicy={mode:'single'|'average';sources:ExchangeSource[]};
export const defaultExchangePolicy:ExchangePolicy={mode:'single',sources:['wallex']};
export type ExchangeSample={source:ExchangeSource;rateRial:string;receivedAt:string};
export type ExchangeRate={status:'ready';base:'USDT';quote:'IRR';rateRial:string;rateToman:string;receivedAt:string;expiresAt:string}|{status:'unavailable';base:'USDT';quote:'IRR';error:string};
export const EXCHANGE_MAX_AGE=90000;
const scale=BigInt(10)**BigInt(12);

export function parseExchangePolicy(value:unknown):ExchangePolicy{
 if(!value||typeof value!=='object'||Array.isArray(value))throw new Error('invalid_exchange_policy');
 const p=value as Record<string,unknown>;
 if(!['single','average'].includes(String(p.mode))||!Array.isArray(p.sources)||p.sources.some(s=>!exchangeSources.includes(s))||new Set(p.sources).size!==p.sources.length||p.sources.length>(exchangeSources.length))throw new Error('invalid_exchange_policy');
 if(p.mode==='single'?p.sources.length!==1:p.sources.length<2)throw new Error('invalid_exchange_selection');
 return {mode:p.mode as ExchangePolicy['mode'],sources:[...p.sources] as ExchangeSource[]};
}

function decimal(value:unknown):bigint|null{
 if(typeof value!=='string'||!/^\d{1,15}(?:\.\d{1,12})?$/.test(value))return null;
 const [whole,fraction='']=value.split('.'),amount=BigInt(whole)*scale+BigInt(fraction.padEnd(12,'0'));
 return amount>BigInt(0)?amount:null;
}
function asDecimal(value:bigint){const fraction=(value%scale).toString().padStart(12,'0').replace(/0+$/,'');return `${value/scale}${fraction?'.'+fraction:''}`}
export function tomanRateToRial(value:unknown):string|null{const amount=decimal(value);return amount===null?null:asDecimal(amount*BigInt(10))}
export function exchangeUnavailable():ExchangeRate{return {status:'unavailable',base:'USDT',quote:'IRR',error:'نرخ تبدیل در دسترس نیست. کمی بعد دوباره تلاش کنید.'}}

/** Equal arithmetic mean in IRR; all selected sources must be fresh. No silent fallback. */
export function combineExchangeRates(policy:ExchangePolicy,samples:ExchangeSample[],now=Date.now()):ExchangeRate{
 try{policy=parseExchangePolicy(policy)}catch{return exchangeUnavailable()}
 const selected=policy.sources.map(source=>samples.filter(sample=>sample.source===source));
 if(selected.some(rows=>rows.length!==1))return exchangeUnavailable();
 const rows=selected.map(([row])=>({...row,amount:decimal(row.rateRial),at:Date.parse(row.receivedAt)}));
 if(rows.some(row=>row.amount===null||!Number.isFinite(row.at)||row.at>now+5000||now-row.at>EXCHANGE_MAX_AGE))return exchangeUnavailable();
 const denominator=scale*BigInt(rows.length),sum=rows.reduce((total,row)=>total+row.amount!,BigInt(0));
 const rial=(sum+denominator/BigInt(2))/denominator;
 if(rial<=BigInt(0)||rial>BigInt(Number.MAX_SAFE_INTEGER))return exchangeUnavailable();
 const at=Math.min(...rows.map(row=>row.at)),fraction=rial%BigInt(10);
 return {status:'ready',base:'USDT',quote:'IRR',rateRial:String(rial),rateToman:`${rial/BigInt(10)}${fraction?'.'+fraction:''}`,receivedAt:new Date(at).toISOString(),expiresAt:new Date(at+EXCHANGE_MAX_AGE).toISOString()};
}
export function exchangeRateFresh(data:ExchangeRate|null,now=Date.now()):data is Extract<ExchangeRate,{status:'ready'}>{
 if(data?.status!=='ready'||data.base!=='USDT'||data.quote!=='IRR'||!/^\d+$/.test(data.rateRial)||decimal(data.rateToman)===null)return false;
 const at=Date.parse(data.receivedAt),expiry=Date.parse(data.expiresAt);
 return Number(data.rateRial)>0&&tomanRateToRial(data.rateToman)===data.rateRial&&Number.isFinite(at)&&at<=now+5000&&expiry===at+EXCHANGE_MAX_AGE&&now<expiry;
}
