<?php
declare(strict_types=1);
namespace App\Services;
use App\Core\Database;
use InvalidArgumentException;
use PDO;
use RuntimeException;
final class StudioApiService
{
    public function modules(): array { return Database::connection()->query("SELECT id,code,name FROM axiom_studio_modules WHERE archived_at IS NULL ORDER BY name")->fetchAll(PDO::FETCH_ASSOC) ?: []; }
    public function definitions(int $moduleId): array { $q=Database::connection()->prepare("SELECT d.*,e.name entity_name,e.table_name FROM axiom_api_definitions d JOIN axiom_studio_entities e ON e.id=d.entity_id WHERE d.module_id=:m AND d.archived_at IS NULL ORDER BY d.name");$q->execute(['m'=>$moduleId]);return $q->fetchAll(PDO::FETCH_ASSOC)?:[]; }
    public function definition(int $id): ?array { $q=Database::connection()->prepare("SELECT d.*,e.name entity_name,e.table_name FROM axiom_api_definitions d JOIN axiom_studio_entities e ON e.id=d.entity_id WHERE d.id=:id AND d.archived_at IS NULL");$q->execute(['id'=>$id]);$r=$q->fetch(PDO::FETCH_ASSOC);return $r?:null; }
    public function entities(int $moduleId): array { $q=Database::connection()->prepare("SELECT id,name,code,table_name FROM axiom_studio_entities WHERE module_id=:m AND archived_at IS NULL ORDER BY position,id");$q->execute(['m'=>$moduleId]);return $q->fetchAll(PDO::FETCH_ASSOC)?:[]; }
    public function keys(int $definitionId): array { $q=Database::connection()->prepare("SELECT id,label,token_prefix,expires_at,last_used_at,revoked_at,created_at FROM axiom_api_keys WHERE api_definition_id=:d ORDER BY id DESC");$q->execute(['d'=>$definitionId]);return $q->fetchAll(PDO::FETCH_ASSOC)?:[]; }
    public function logs(int $definitionId,int $limit=40): array { $q=Database::connection()->prepare("SELECT * FROM axiom_api_logs WHERE api_definition_id=:d ORDER BY id DESC LIMIT ".max(1,min(200,$limit)));$q->execute(['d'=>$definitionId]);return $q->fetchAll(PDO::FETCH_ASSOC)?:[]; }
    public function save(int $moduleId,array $data,?int $userId): int {
        $name=trim((string)($data['name']??''));$entityId=(int)($data['entity_id']??0);if($moduleId<1||$entityId<1||$name==='')throw new InvalidArgumentException('Module, entité et nom sont obligatoires.');
        $methods=[];foreach(['get','post','put','delete'] as $m)$methods[$m]=isset($data['allow_'.$m]);
        $id=(int)($data['definition_id']??0);$pdo=Database::connection();$code=$this->code((string)($data['code']??$name));
        if($id>0){$q=$pdo->prepare("UPDATE axiom_api_definitions SET name=:n,description=:d,entity_id=:e,allow_get=:g,allow_post=:p,allow_put=:u,allow_delete=:x,rate_limit_minute=:r,status='draft',updated_by=:uid,updated_at=CURRENT_TIMESTAMP WHERE id=:id AND module_id=:m AND archived_at IS NULL");$q->execute(['n'=>$name,'d'=>trim((string)($data['description']??''))?:null,'e'=>$entityId,'g'=>(int)$methods['get'],'p'=>(int)$methods['post'],'u'=>(int)$methods['put'],'x'=>(int)$methods['delete'],'r'=>max(1,min(10000,(int)($data['rate_limit_minute']??100))),'uid'=>$userId,'id'=>$id,'m'=>$moduleId]);return $id;}
        $code=$this->uniqueCode($code);$q=$pdo->prepare("INSERT INTO axiom_api_definitions(module_id,entity_id,name,code,description,allow_get,allow_post,allow_put,allow_delete,rate_limit_minute,status,created_by,updated_by) VALUES(:m,:e,:n,:c,:d,:g,:p,:u,:x,:r,'draft',:uid,:uid)");$q->execute(['m'=>$moduleId,'e'=>$entityId,'n'=>$name,'c'=>$code,'d'=>trim((string)($data['description']??''))?:null,'g'=>(int)$methods['get'],'p'=>(int)$methods['post'],'u'=>(int)$methods['put'],'x'=>(int)$methods['delete'],'r'=>max(1,min(10000,(int)($data['rate_limit_minute']??100))),'uid'=>$userId]);return (int)$pdo->lastInsertId();
    }
    public function publish(int $id,?int $userId): void {$q=Database::connection()->prepare("UPDATE axiom_api_definitions SET status='published',published_at=CURRENT_TIMESTAMP,updated_by=:u,updated_at=CURRENT_TIMESTAMP WHERE id=:id AND archived_at IS NULL");$q->execute(['u'=>$userId,'id'=>$id]);}
    public function archive(int $id,?int $userId): void {$q=Database::connection()->prepare("UPDATE axiom_api_definitions SET archived_at=CURRENT_TIMESTAMP,updated_by=:u,updated_at=CURRENT_TIMESTAMP WHERE id=:id");$q->execute(['u'=>$userId,'id'=>$id]);}
    public function createKey(int $definitionId,string $label,?string $expiresAt,?int $userId): string {if(!$this->definition($definitionId))throw new RuntimeException('API introuvable.');$plain='axm_'.bin2hex(random_bytes(24));$hash=hash('sha256',$plain);$q=Database::connection()->prepare("INSERT INTO axiom_api_keys(api_definition_id,label,token_hash,token_prefix,expires_at,created_by) VALUES(:d,:l,:h,:p,:e,:u)");$q->execute(['d'=>$definitionId,'l'=>trim($label)?:'Clé API','h'=>$hash,'p'=>substr($plain,0,12),'e'=>trim((string)$expiresAt)?:null,'u'=>$userId]);return $plain;}
    public function revokeKey(int $id): void {$q=Database::connection()->prepare("UPDATE axiom_api_keys SET revoked_at=CURRENT_TIMESTAMP WHERE id=:id AND revoked_at IS NULL");$q->execute(['id'=>$id]);}
    public function openApi(int $id): array {$d=$this->definition($id);if(!$d)throw new RuntimeException('API introuvable.');$path='/public/api/axiom?api='.$d['code'];$paths=[];foreach(['get','post','put','delete'] as $m){if((int)$d['allow_'.$m]===1)$paths[$m]=['summary'=>strtoupper($m).' '.$d['entity_name'],'security'=>[['bearerAuth'=>[]]],'responses'=>['200'=>['description'=>'Réponse JSON AXIOM']]];}return ['openapi'=>'3.0.3','info'=>['title'=>$d['name'],'version'=>'1.0.0','description'=>$d['description']??''],'paths'=>[$path=>$paths],'components'=>['securitySchemes'=>['bearerAuth'=>['type'=>'http','scheme'=>'bearer']]]];}
    private function code(string $v): string {$v=iconv('UTF-8','ASCII//TRANSLIT//IGNORE',$v)?:$v;$v=strtolower(trim(preg_replace('/[^a-zA-Z0-9]+/','_',$v),'_'));return $v?:'api';}
    private function uniqueCode(string $base): string {$c=$base;$i=2;$q=Database::connection()->prepare("SELECT COUNT(*) FROM axiom_api_definitions WHERE code=:c AND archived_at IS NULL");while(true){$q->execute(['c'=>$c]);if((int)$q->fetchColumn()===0)return $c;$c=$base.'_'.$i++;}}
}
