<?php
declare(strict_types=1);
namespace Aen\RateLab;
use Predis\Client;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\RedisStore;
use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\RateLimiter\Storage\CacheStorage;
use Symfony\Component\RateLimiter\Storage\InMemoryStorage;
final class Lab {
 public static function factory(array $config,string $backend='memory',?string $namespace=null,bool $locked=true,bool $slow=false):RateLimiterFactory {
  $namespace??=getenv('AEN_NAMESPACE')?:'aen_rate_lab_v1';
  if(!preg_match('/^[a-zA-Z0-9_]{1,60}$/D',$namespace))throw new \InvalidArgumentException('Invalid namespace');
  $locks=null;
  if($backend==='memory')$storage=new InMemoryStorage();
  elseif($backend==='files'){
   $base=dirname(__DIR__).'/var';if(!is_dir($base.'/locks')&&!@mkdir($base.'/locks',0770,true)&&!is_dir($base.'/locks'))throw new \RuntimeException('Cannot create lock directory');
   $storage=new CacheStorage(new FilesystemAdapter($namespace,0,$base.'/cache'));
   if($locked)$locks=new LockFactory(new FlockStore($base.'/locks'));
  }elseif($backend==='redis'){
   $redis=new Client(getenv('REDIS_URL')?:'tcp://127.0.0.1:6385',['parameters'=>['timeout'=>2,'read_write_timeout'=>2]]);
   $storage=new RedisStateStorage($redis,$namespace);
   if($locked)$locks=new LockFactory(new RedisStore($redis));
  }else throw new \InvalidArgumentException('Unknown backend');
  $config['id']=$namespace.'_'.$config['id'];
  if($slow)$storage=new DelayedStorage($storage);
  return new RateLimiterFactory($config,$storage,$locks);
 }
 public static function result(\Symfony\Component\RateLimiter\RateLimit $r,int $cost=1):array {
  return ['accepted'=>$r->isAccepted(),'remaining'=>$r->getRemainingTokens(),'limit'=>$r->getLimit(),'cost'=>$cost,'retry_after_seconds'=>max(0,$r->getRetryAfter()->getTimestamp()-time())];
 }
 public static function policy(string $policy,int $limit,int $seconds=2,int $refill=1):array {
  if(!in_array($policy,['fixed_window','sliding_window','token_bucket'],true)||$limit<1||$limit>100||$seconds<1||$seconds>60||$refill<1||$refill>100)throw new \InvalidArgumentException('Config out of range');
  return ['id'=>'demo','policy'=>$policy,'limit'=>$limit]+($policy==='token_bucket'?['rate'=>['interval'=>"$seconds seconds",'amount'=>$refill]]:['interval'=>"$seconds seconds"]);
 }
}
