<?php
declare(strict_types=1);

namespace App\Services;

use App\Core\Database;
use InvalidArgumentException;
use PDO;
use RuntimeException;
use ZipArchive;

final class StudioCrudGeneratorService
{
    public function modules(): array
    {
        $s=Database::connection()->query("SELECT id,code,name,version,status FROM axiom_studio_modules WHERE archived_at IS NULL ORDER BY name");
        return $s->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    public function module(int $id): ?array
    {
        $s=Database::connection()->prepare("SELECT * FROM axiom_studio_modules WHERE id=:id AND archived_at IS NULL");
        $s->execute(['id'=>$id]); $r=$s->fetch(PDO::FETCH_ASSOC); return $r?:null;
    }

    public function entities(int $moduleId): array
    {
        $s=Database::connection()->prepare("SELECT * FROM axiom_studio_entities WHERE module_id=:id AND archived_at IS NULL ORDER BY position,id");
        $s->execute(['id'=>$moduleId]); return $s->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    public function entity(int $id): ?array
    {
        $s=Database::connection()->prepare("SELECT * FROM axiom_studio_entities WHERE id=:id AND archived_at IS NULL");
        $s->execute(['id'=>$id]); $r=$s->fetch(PDO::FETCH_ASSOC); return $r?:null;
    }

    public function fields(int $entityId): array
    {
        $s=Database::connection()->prepare("SELECT * FROM axiom_studio_fields WHERE entity_id=:id AND archived_at IS NULL ORDER BY position,id");
        $s->execute(['id'=>$entityId]); return $s->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    public function formComponents(int $entityId): array
    {
        $sql="SELECT c.*,f.code AS field_code,f.field_type FROM axiom_studio_forms x JOIN axiom_studio_form_components c ON c.form_id=x.id LEFT JOIN axiom_studio_fields f ON f.id=c.field_id WHERE x.entity_id=:id AND x.archived_at IS NULL AND c.archived_at IS NULL ORDER BY c.position,c.id";
        $s=Database::connection()->prepare($sql); $s->execute(['id'=>$entityId]); return $s->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    public function history(int $moduleId): array
    {
        $s=Database::connection()->prepare("SELECT r.*,e.name entity_name FROM axiom_generation_runs r LEFT JOIN axiom_studio_entities e ON e.id=r.entity_id WHERE r.module_id=:id ORDER BY r.id DESC LIMIT 30");
        $s->execute(['id'=>$moduleId]); return $s->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    public function preview(int $moduleId,int $entityId): array
    {
        [$module,$entity,$fields,$components]=$this->context($moduleId,$entityId);
        $files=$this->buildFiles($module,$entity,$fields,$components);
        return [
            'module'=>$module,'entity'=>$entity,'fields'=>$fields,'components'=>$components,
            'files'=>array_keys($files),'routes'=>$this->routes($module,$entity),
            'permissions'=>$this->permissions($module,$entity),
            'snippets'=>array_slice($files,0,3,true),
        ];
    }

    public function generate(int $moduleId,int $entityId,?int $userId): array
    {
        [$module,$entity,$fields,$components]=$this->context($moduleId,$entityId);
        $files=$this->buildFiles($module,$entity,$fields,$components);
        $slug=$this->slug((string)$module['code']); $entitySlug=$this->slug((string)$entity['code']);
        $target=BASE_PATH.'/storage/generated/'.$slug.'/'.$entitySlug;
        if(!is_dir($target) && !mkdir($target,0775,true) && !is_dir($target)) throw new RuntimeException('Impossible de créer le répertoire de génération.');
        $pdo=Database::connection(); $pdo->beginTransaction();
        try {
            $s=$pdo->prepare("INSERT INTO axiom_generation_runs(module_id,entity_id,generator_code,status,output_path,axiom_version,created_by) VALUES(:m,:e,'crud-v1','running',:p,:v,:u)");
            $s->execute(['m'=>$moduleId,'e'=>$entityId,'p'=>$target,'v'=>$this->version(),'u'=>$userId]); $runId=(int)$pdo->lastInsertId();
            $artifact=$pdo->prepare("INSERT INTO axiom_generation_artifacts(run_id,module_id,entity_id,relative_path,sha256,size_bytes,artifact_type,created_by) VALUES(:r,:m,:e,:p,:h,:s,:t,:u)");
            foreach($files as $relative=>$content){
                $absolute=$target.'/'.$relative; $dir=dirname($absolute); if(!is_dir($dir)) mkdir($dir,0775,true);
                if(file_put_contents($absolute,$content)===false) throw new RuntimeException('Écriture impossible : '.$relative);
                $artifact->execute(['r'=>$runId,'m'=>$moduleId,'e'=>$entityId,'p'=>$relative,'h'=>hash('sha256',$content),'s'=>strlen($content),'t'=>$this->type($relative),'u'=>$userId]);
            }
            $zipPath=$target.'/'.strtoupper($slug).'_'.$entitySlug.'_CRUD.zip';
            if(class_exists(ZipArchive::class)){
                $zip=new ZipArchive(); if($zip->open($zipPath,ZipArchive::CREATE|ZipArchive::OVERWRITE)!==true) throw new RuntimeException('Création ZIP impossible.');
                foreach($files as $relative=>$content){$zip->addFromString($relative,$content);} $zip->close();
            }
            $pdo->prepare("UPDATE axiom_generation_runs SET status='completed',artifact_count=:n,package_path=:z,completed_at=CURRENT_TIMESTAMP WHERE id=:id")->execute(['n'=>count($files),'z'=>is_file($zipPath)?$zipPath:null,'id'=>$runId]);
            $pdo->commit();
            return ['run_id'=>$runId,'target'=>$target,'zip'=>is_file($zipPath)?$zipPath:null,'count'=>count($files)];
        } catch (\Throwable $e) {
            if($pdo->inTransaction()) $pdo->rollBack(); throw $e;
        }
    }

    private function context(int $moduleId,int $entityId): array
    {
        $module=$this->module($moduleId); $entity=$this->entity($entityId);
        if(!$module) throw new InvalidArgumentException('Module Studio introuvable.');
        if(!$entity || (int)$entity['module_id']!==$moduleId) throw new InvalidArgumentException('Entité Studio invalide.');
        $fields=$this->fields($entityId); if(!$fields) throw new InvalidArgumentException('Ajoutez au moins un champ métier avant la génération.');
        return [$module,$entity,$fields,$this->formComponents($entityId)];
    }

    private function buildFiles(array $module,array $entity,array $fields,array $components): array
    {
        $class=$this->className((string)$entity['code']); $moduleSlug=$this->slug((string)$module['code']); $entitySlug=$this->slug((string)$entity['code']);
        $table=(string)$entity['table_name']; $fieldCodes=array_map(fn($f)=>(string)$f['code'],$fields);
        $fillable=implode(', ',array_map(fn($c)=>"'".$c."'",$fieldCodes));
        $columns=implode("\n",array_map(fn($f)=>"            '".$f['code']."' => '".$f['field_type']."',",$fields));
        $routes=$this->routes($module,$entity); $permissions=$this->permissions($module,$entity);
        $controller="<?php\ndeclare(strict_types=1);\n\nnamespace App\\Modules\\Generated\\{$class}\\Controllers;\n\nfinal class {$class}Controller\n{\n    public function index(): void {}\n    public function create(): void {}\n    public function store(): void {}\n    public function show(): void {}\n    public function edit(): void {}\n    public function update(): void {}\n    public function archive(): void {}\n}\n";
        $repository="<?php\ndeclare(strict_types=1);\n\nnamespace App\\Modules\\Generated\\{$class}\\Repositories;\n\nuse App\\Core\\Database;\nuse PDO;\n\nfinal class {$class}Repository\n{\n    private string \$table='{$table}';\n    public function paginate(string \$search='',int \$limit=25,int \$offset=0): array {\n        \$sql='SELECT * FROM `'.\$this->table.'` WHERE archived_at IS NULL ORDER BY id DESC LIMIT :limit OFFSET :offset';\n        \$s=Database::connection()->prepare(\$sql); \$s->bindValue('limit',\$limit,PDO::PARAM_INT); \$s->bindValue('offset',\$offset,PDO::PARAM_INT); \$s->execute(); return \$s->fetchAll(PDO::FETCH_ASSOC)?:[];\n    }\n}\n";
        $service="<?php\ndeclare(strict_types=1);\n\nnamespace App\\Modules\\Generated\\{$class}\\Services;\n\nfinal class {$class}Service\n{\n    private array \$fillable=[{$fillable}];\n    public function sanitize(array \$input): array { return array_intersect_key(\$input,array_flip(\$this->fillable)); }\n    public function schema(): array { return [\n{$columns}\n        ]; }\n}\n";
        $viewIndex="<main class=\"generated-crud\"><h2>".htmlspecialchars((string)$entity['name'],ENT_QUOTES)."</h2><form method=\"get\"><input name=\"q\" placeholder=\"Rechercher\"><button>Rechercher</button></form><table><thead><tr>".implode('',array_map(fn($f)=>'<th>'.htmlspecialchars((string)$f['label'],ENT_QUOTES).'</th>',$fields))."<th>Actions</th></tr></thead><tbody><!-- données générées --></tbody></table></main>\n";
        $viewForm="<main class=\"generated-crud\"><h2>".htmlspecialchars((string)$entity['name'],ENT_QUOTES)."</h2><form method=\"post\">".implode('',array_map(fn($f)=>'<label>'.htmlspecialchars((string)$f['label'],ENT_QUOTES).'<input name="'.htmlspecialchars((string)$f['code'],ENT_QUOTES).'"'.(!empty($f['is_required'])?' required':'').'></label>',$fields))."<button>Enregistrer</button></form></main>\n";
        $manifest=json_encode(['schema'=>'axiom.generated-module/1.0','module'=>$moduleSlug,'entity'=>$entitySlug,'version'=>$module['version'],'generator'=>'crud-v1','generated_at'=>date(DATE_ATOM),'files'=>['controller','service','repository','views','routes','permissions']],JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)."\n";
        return [
            "app/Modules/Generated/{$class}/Controllers/{$class}Controller.php"=>$controller,
            "app/Modules/Generated/{$class}/Repositories/{$class}Repository.php"=>$repository,
            "app/Modules/Generated/{$class}/Services/{$class}Service.php"=>$service,
            "app/Modules/Generated/{$class}/Views/index.php"=>$viewIndex,
            "app/Modules/Generated/{$class}/Views/form.php"=>$viewForm,
            "config/routes.generated.php"=>"<?php\nreturn ".var_export($routes,true).";\n",
            "config/permissions.generated.json"=>json_encode($permissions,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)."\n",
            "module.json"=>$manifest,
            "README.md"=>"# {$entity['name']}\n\nCRUD généré par AXIOM Studio {$this->version()}.\n\nLes fichiers sont placés dans `storage/generated` et ne modifient jamais automatiquement les routes de production.\n",
        ];
    }

    private function routes(array $module,array $entity): array
    {
        $base='/modules/'.$this->slug((string)$module['code']).'/'.$this->slug((string)$entity['code']);
        return [['GET',$base,'index'],['GET',$base.'/nouveau','create'],['POST',$base,'store'],['GET',$base.'/voir','show'],['GET',$base.'/modifier','edit'],['POST',$base.'/modifier','update'],['POST',$base.'/archiver','archive']];
    }
    private function permissions(array $module,array $entity): array { $p=$this->slug((string)$module['code']).'.'.$this->slug((string)$entity['code']); return array_map(fn($a)=>$p.'.'.$a,['view','create','update','archive','delete']); }
    private function className(string $v): string { return implode('',array_map('ucfirst',preg_split('/[^a-z0-9]+/i',$v,-1,PREG_SPLIT_NO_EMPTY)?:['Entity'])); }
    private function slug(string $v): string { $v=strtolower(trim($v)); $v=preg_replace('/[^a-z0-9]+/','-',$v)??''; return trim($v,'-')?:'module'; }
    private function type(string $p): string { $e=strtolower(pathinfo($p,PATHINFO_EXTENSION)); return $e==='php'?'php':($e==='json'?'json':($e==='md'?'documentation':'config')); }
    private function version(): string { return is_file(BASE_PATH.'/VERSION')?trim((string)file_get_contents(BASE_PATH.'/VERSION')):'9.4.3'; }
}
