<?php
declare(strict_types=1);
namespace App\Services;
use App\Core\Database;
use PDO;
use RuntimeException;
final class ApiGatewayService
{
    private string $correlationId = '';
    public function handle(string $apiCode,string $method,array $query,array $payload,string $token,string $ip,string $correlationId): array {
        $this->correlationId=$correlationId;$started=microtime(true);$pdo=Database::connection();$d=$this->definition($apiCode);if(!$d)return $this->result(404,['error'=>'API introuvable','correlation_id'=>$correlationId]);
        $key=$this->authenticate((int)$d['id'],$token);if(!$key)return $this->logResult($d,null,$method,$ip,$started,401,['error'=>'Clé API invalide ou expirée']);
        if(!$this->rateAllowed((int)$key['id'],(int)$d['rate_limit_minute']))return $this->logResult($d,$key,$method,$ip,$started,429,['error'=>'Limite de débit dépassée']);
        $m=strtolower($method);if(!in_array($m,['get','post','put','delete'],true)||(int)$d['allow_'.$m]!==1)return $this->logResult($d,$key,$method,$ip,$started,405,['error'=>'Méthode non autorisée']);
        try{$table=$this->identifier((string)$d['table_name']);$fields=$this->fields((int)$d['entity_id']);$allowed=array_column($fields,'code','code');$id=max(0,(int)($query['id']??$payload['id']??0));
            if($m==='get'){$limit=max(1,min(200,(int)($query['limit']??50)));$sql="SELECT * FROM `$table`";$params=[];if($id>0){$sql.=' WHERE id=:id';$params['id']=$id;}$sql.=' LIMIT '.$limit;$q=$pdo->prepare($sql);$q->execute($params);$body=['data'=>$q->fetchAll(PDO::FETCH_ASSOC)?:[]];}
            elseif($m==='post'){$clean=$this->clean($payload,$allowed);if(!$clean)throw new RuntimeException('Aucune donnée exploitable.');$cols=array_keys($clean);$sql="INSERT INTO `$table` (`".implode('`,`',$cols)."`) VALUES (:".implode(',:',$cols).')';$q=$pdo->prepare($sql);$q->execute($clean);$body=['id'=>(int)$pdo->lastInsertId(),'created'=>true];}
            elseif($m==='put'){if($id<1)throw new RuntimeException('Identifiant requis.');$clean=$this->clean($payload,$allowed);unset($clean['id']);if(!$clean)throw new RuntimeException('Aucune donnée exploitable.');$set=[];foreach(array_keys($clean) as $c)$set[]="`$c`=:$c";$clean['__id']=$id;$q=$pdo->prepare("UPDATE `$table` SET ".implode(',',$set).' WHERE id=:__id');$q->execute($clean);$body=['updated'=>$q->rowCount()];}
            else{if($id<1)throw new RuntimeException('Identifiant requis.');$q=$pdo->prepare("DELETE FROM `$table` WHERE id=:id");$q->execute(['id'=>$id]);$body=['deleted'=>$q->rowCount()];}
            return $this->logResult($d,$key,$method,$ip,$started,200,$body);
        }catch(\Throwable $e){return $this->logResult($d,$key,$method,$ip,$started,422,['error'=>$e->getMessage()]);}
    }
    private function definition(string $code): ?array {$q=Database::connection()->prepare("SELECT d.*,e.table_name FROM axiom_api_definitions d JOIN axiom_studio_entities e ON e.id=d.entity_id WHERE d.code=:c AND d.status='published' AND d.archived_at IS NULL");$q->execute(['c'=>$code]);$r=$q->fetch(PDO::FETCH_ASSOC);return $r?:null;}
    private function authenticate(int $definitionId,string $token): ?array {if($token==='')return null;$q=Database::connection()->prepare("SELECT * FROM axiom_api_keys WHERE api_definition_id=:d AND token_hash=:h AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at>CURRENT_TIMESTAMP)");$q->execute(['d'=>$definitionId,'h'=>hash('sha256',$token)]);$r=$q->fetch(PDO::FETCH_ASSOC);if($r)Database::connection()->prepare("UPDATE axiom_api_keys SET last_used_at=CURRENT_TIMESTAMP WHERE id=:id")->execute(['id'=>$r['id']]);return $r?:null;}
    private function fields(int $entityId): array {$q=Database::connection()->prepare("SELECT code FROM axiom_studio_fields WHERE entity_id=:e AND archived_at IS NULL");$q->execute(['e'=>$entityId]);return $q->fetchAll(PDO::FETCH_ASSOC)?:[];}
    private function clean(array $payload,array $allowed): array {$out=[];foreach($payload as $k=>$v){if(isset($allowed[$k])&&preg_match('/^[a-zA-Z0-9_]+$/',$k))$out[$k]=is_scalar($v)||$v===null?$v:json_encode($v,JSON_UNESCAPED_UNICODE);}return $out;}
    private function rateAllowed(int $keyId,int $limit): bool {$q=Database::connection()->prepare("SELECT COUNT(*) FROM axiom_api_logs WHERE api_key_id=:k AND created_at>=DATE_SUB(CURRENT_TIMESTAMP,INTERVAL 1 MINUTE)");$q->execute(['k'=>$keyId]);return (int)$q->fetchColumn()<$limit;}
    private function logResult(array $d,?array $key,string $method,string $ip,float $started,int $status,array $body): array {$duration=(int)round((microtime(true)-$started)*1000);$body['correlation_id']=$this->correlationId;$q=Database::connection()->prepare("INSERT INTO axiom_api_logs(api_definition_id,api_key_id,http_method,endpoint,status_code,duration_ms,ip_address,response_excerpt) VALUES(:d,:k,:m,:e,:s,:t,:ip,:r)");$q->execute(['d'=>$d['id'],'k'=>$key['id']??null,'m'=>strtoupper($method),'e'=>(string)$d['code'],'s'=>$status,'t'=>$duration,'ip'=>substr(hash('sha256',$ip.'|'.(string)($_ENV['SECURITY_LOG_HASH_SALT']??'axiom')),0,64),'r'=>substr(json_encode($body,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),0,1000)]);return $this->result($status,$body);}
    private function result(int $status,array $body): array {return ['status'=>$status,'body'=>$body];}
    private function identifier(string $v): string {if(!preg_match('/^[a-zA-Z0-9_]+$/',$v))throw new RuntimeException('Identifiant SQL invalide.');return $v;}
}
