<?php
declare(strict_types=1);

namespace App\Services;

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

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

    public function categories(): array
    {
        $q=Database::connection()->query("SELECT id,code,name FROM axiom_document_categories WHERE archived_at IS NULL ORDER BY sort_order,name");
        return $q->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    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 fields(int $entityId): array
    {
        if ($entityId < 1) {
            return [];
        }

        // Le Document Designer consomme désormais le service de métadonnées
        // canonique du Studio au lieu de dépendre du schéma physique SQL.
        $metadataFields = (new StudioEntityService())->fields($entityId);

        return array_map(
            static fn(array $field): array => [
                'id' => (int)($field['id'] ?? 0),
                'name' => (string)($field['label'] ?? $field['code'] ?? ''),
                'label' => (string)($field['label'] ?? $field['code'] ?? ''),
                'code' => (string)($field['code'] ?? ''),
                // Compatibilité avec les consommateurs historiques du Document Designer.
                'column_name' => (string)($field['code'] ?? ''),
                'field_type' => (string)($field['field_type'] ?? 'string'),
                'position' => (int)($field['position'] ?? 0),
            ],
            $metadataFields
        );
    }

    public function templates(int $moduleId): array
    {
        $q=Database::connection()->prepare("SELECT t.*,c.name category_name,(SELECT COUNT(*) FROM axiom_document_template_versions v WHERE v.template_id=t.id) version_count FROM axiom_document_templates t LEFT JOIN axiom_document_categories c ON c.id=t.category_id WHERE t.module_id=:m AND t.archived_at IS NULL ORDER BY t.updated_at DESC,t.id DESC");
        $q->execute(['m'=>$moduleId]);
        return $q->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    public function template(int $id): ?array
    {
        $q=Database::connection()->prepare("SELECT t.*,c.name category_name FROM axiom_document_templates t LEFT JOIN axiom_document_categories c ON c.id=t.category_id WHERE t.id=:id AND t.archived_at IS NULL");
        $q->execute(['id'=>$id]);$row=$q->fetch(PDO::FETCH_ASSOC);
        if(!$row)return null;
        $row['page_config']=json_decode((string)($row['page_config_json']??'{}'),true) ?: [];
        return $row;
    }

    public function versions(int $templateId): array
    {
        $q=Database::connection()->prepare("SELECT * FROM axiom_document_template_versions WHERE template_id=:t ORDER BY version_no DESC,id DESC");
        $q->execute(['t'=>$templateId]);return $q->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    public function generated(int $templateId,int $limit=25): array
    {
        $limit=max(1,min(100,$limit));
        $q=Database::connection()->prepare("SELECT * FROM axiom_document_generated WHERE template_id=:t ORDER BY id DESC LIMIT {$limit}");
        $q->execute(['t'=>$templateId]);return $q->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    public function save(int $moduleId,array $data,?int $userId): int
    {
        $name=trim((string)($data['name']??''));$body=trim((string)($data['body_html']??''));
        if($moduleId<1||$name===''||$body==='')throw new InvalidArgumentException('Module, nom et contenu sont obligatoires.');
        $format=in_array(($data['output_format']??'pdf'),['pdf','html','email'],true)?(string)$data['output_format']:'pdf';
        $orientation=in_array(($data['orientation']??'portrait'),['portrait','landscape'],true)?(string)$data['orientation']:'portrait';
        $size=in_array(($data['page_size']??'A4'),['A4','A3','Letter'],true)?(string)$data['page_size']:'A4';
        $page=['size'=>$size,'orientation'=>$orientation,'margin_top'=>max(0,min(80,(int)($data['margin_top']??15))),'margin_right'=>max(0,min(80,(int)($data['margin_right']??15))),'margin_bottom'=>max(0,min(80,(int)($data['margin_bottom']??15))),'margin_left'=>max(0,min(80,(int)($data['margin_left']??15))),'header_html'=>(string)($data['header_html']??''),'footer_html'=>(string)($data['footer_html']??'')];
        $id=(int)($data['template_id']??0);$pdo=Database::connection();
        if($id>0){
            $current=$this->template($id);if(!$current||((int)$current['module_id']!==$moduleId))throw new RuntimeException('Modèle introuvable.');
            $this->snapshot($current,$userId);
            $next=(int)$current['version_no']+1;
            $q=$pdo->prepare("UPDATE axiom_document_templates SET name=:n,description=:d,category_id=:cat,output_format=:f,body_html=:b,subject_template=:s,entity_id=:e,page_config_json=:p,version_no=:v,status='draft',active=0,published_at=NULL,updated_by=:u,updated_at=CURRENT_TIMESTAMP WHERE id=:id");
            $q->execute(['n'=>$name,'d'=>trim((string)($data['description']??''))?:null,'cat'=>(int)($data['category_id']??0)?:null,'f'=>$format,'b'=>$this->sanitizeHtml($body),'s'=>trim((string)($data['subject_template']??''))?:null,'e'=>(int)($data['entity_id']??0)?:null,'p'=>json_encode($page,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),'v'=>$next,'u'=>$userId,'id'=>$id]);return $id;
        }
        $code=$this->uniqueCode($this->code((string)($data['code']??$name)));
        $q=$pdo->prepare("INSERT INTO axiom_document_templates(module_id,category_id,entity_id,code,name,description,output_format,body_html,subject_template,page_config_json,version_no,status,active,created_by,updated_by) VALUES(:m,:cat,:e,:c,:n,:d,:f,:b,:s,:p,1,'draft',0,:u,:u)");
        $q->execute(['m'=>$moduleId,'cat'=>(int)($data['category_id']??0)?:null,'e'=>(int)($data['entity_id']??0)?:null,'c'=>$code,'n'=>$name,'d'=>trim((string)($data['description']??''))?:null,'f'=>$format,'b'=>$this->sanitizeHtml($body),'s'=>trim((string)($data['subject_template']??''))?:null,'p'=>json_encode($page,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),'u'=>$userId]);
        return (int)$pdo->lastInsertId();
    }

    public function publish(int $id,?int $userId): void
    {
        $t=$this->template($id);if(!$t)throw new RuntimeException('Modèle introuvable.');
        $this->snapshot($t,$userId);
        $q=Database::connection()->prepare("UPDATE axiom_document_templates SET status='published',active=1,published_at=CURRENT_TIMESTAMP,updated_by=:u,updated_at=CURRENT_TIMESTAMP WHERE id=:id");$q->execute(['u'=>$userId,'id'=>$id]);
    }

    public function duplicate(int $id,?int $userId): int
    {
        $t=$this->template($id);if(!$t)throw new RuntimeException('Modèle introuvable.');
        $code=$this->uniqueCode($this->code((string)$t['code'].'_copie'));$name=(string)$t['name'].' — copie';
        $q=Database::connection()->prepare("INSERT INTO axiom_document_templates(module_id,category_id,entity_id,code,name,description,output_format,body_html,subject_template,page_config_json,version_no,status,active,created_by,updated_by) VALUES(:m,:cat,:e,:c,:n,:d,:f,:b,:s,:p,1,'draft',0,:u,:u)");
        $q->execute(['m'=>$t['module_id'],'cat'=>$t['category_id'],'e'=>$t['entity_id'],'c'=>$code,'n'=>$name,'d'=>$t['description'],'f'=>$t['output_format'],'b'=>$t['body_html'],'s'=>$t['subject_template'],'p'=>$t['page_config_json'],'u'=>$userId]);return (int)Database::connection()->lastInsertId();
    }

    public function archive(int $id,?int $userId): void
    {
        $q=Database::connection()->prepare("UPDATE axiom_document_templates SET active=0,status='archived',archived_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 restoreVersion(int $templateId,int $versionId,?int $userId): void
    {
        $q=Database::connection()->prepare("SELECT * FROM axiom_document_template_versions WHERE id=:v AND template_id=:t");$q->execute(['v'=>$versionId,'t'=>$templateId]);$v=$q->fetch(PDO::FETCH_ASSOC);if(!$v)throw new RuntimeException('Version introuvable.');
        $current=$this->template($templateId);if(!$current)throw new RuntimeException('Modèle introuvable.');$this->snapshot($current,$userId);
        $next=(int)$current['version_no']+1;$u=Database::connection()->prepare("UPDATE axiom_document_templates SET name=:n,description=:d,output_format=:f,body_html=:b,subject_template=:s,page_config_json=:p,version_no=:no,status='draft',active=0,published_at=NULL,updated_by=:u,updated_at=CURRENT_TIMESTAMP WHERE id=:id");
        $u->execute(['n'=>$v['name'],'d'=>$v['description'],'f'=>$v['output_format'],'b'=>$v['body_html'],'s'=>$v['subject_template'],'p'=>$v['page_config_json'],'no'=>$next,'u'=>$userId,'id'=>$templateId]);
    }

    public function render(int $templateId,array $data,?int $userId=null,bool $register=false): array
    {
        $t=$this->template($templateId);if(!$t)throw new RuntimeException('Modèle introuvable.');
        $html=$this->resolve((string)$t['body_html'],$data);$subject=$this->resolve((string)($t['subject_template']??''),$data);
        if(!str_contains($html,'logo-carrefour-des-arts')){
            $logo='<div data-cda-document-logo style="margin:0 0 18px"><img src="/public/assets/images/logo-carrefour-des-arts.jpg" alt="Carrefour des Arts" style="display:block;width:150px;height:auto;max-width:35%"></div>';
            $html=$logo.$html;
        }
        if($register){$q=Database::connection()->prepare("INSERT INTO axiom_document_generated(template_id,template_version,reference,output_format,subject_rendered,content_html,data_json,status,generated_by) VALUES(:t,:v,:r,:f,:s,:h,:d,'generated',:u)");$q->execute(['t'=>$templateId,'v'=>$t['version_no'],'r'=>'GEN-'.date('Ymd-His').'-'.strtoupper(substr(bin2hex(random_bytes(4)),0,8)),'f'=>$t['output_format'],'s'=>$subject?:null,'h'=>$html,'d'=>json_encode($data,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),'u'=>$userId]);}
        return ['template'=>$t,'subject'=>$subject,'html'=>$html,'page'=>$t['page_config']];
    }

    public function sampleData(int $entityId): array
    {
        if ($entityId < 1) {
            return ['date' => date('d/m/Y'), 'organisation' => ['nom' => 'Carrefour des Arts']];
        }

        $data = [];
        foreach ($this->fields($entityId) as $field) {
            $code = (string)($field['code'] ?? '');
            if ($code !== '') {
                $data[$code] = 'Exemple ' . $code;
            }
        }
        $data['date'] = date('d/m/Y');
        return $data;
    }

    public function variables(int $entityId): array
    {
        $vars=[['token'=>'{{date}}','label'=>'Date du jour'],['token'=>'{{organisation.nom}}','label'=>'Nom de l’organisation']];
        foreach($this->fields($entityId) as $f)$vars[]=['token'=>'{{'.$f['code'].'}}','label'=>$f['name']];return $vars;
    }

    private function snapshot(array $t,?int $userId): void
    {
        $q=Database::connection()->prepare("INSERT IGNORE INTO axiom_document_template_versions(template_id,version_no,name,description,output_format,subject_template,body_html,page_config_json,status,created_by) VALUES(:t,:v,:n,:d,:f,:s,:b,:p,:st,:u)");
        $q->execute(['t'=>$t['id'],'v'=>$t['version_no'],'n'=>$t['name'],'d'=>$t['description'],'f'=>$t['output_format'],'s'=>$t['subject_template'],'b'=>$t['body_html'],'p'=>$t['page_config_json'],'st'=>$t['status'],'u'=>$userId]);
    }

    private function resolve(string $text,array $data): string
    {
        $text=preg_replace_callback('/\{\{#([a-zA-Z0-9_.-]+)\}\}(.*?)\{\{\/\1\}\}/s',function(array $m)use($data):string{$rows=$this->value($data,$m[1]);if(!is_array($rows))return '';$out='';foreach($rows as $row){$scope=is_array($row)?array_merge($data,$row):array_merge($data,['value'=>$row]);$out.=$this->resolve($m[2],$scope);}return $out;},$text)??$text;
        return preg_replace_callback('/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/',function(array $m)use($data):string{$v=$this->value($data,$m[1]);return htmlspecialchars(is_scalar($v)?(string)$v:json_encode($v,JSON_UNESCAPED_UNICODE),ENT_QUOTES,'UTF-8');},$text)??$text;
    }

    private function value(array $data,string $path): mixed { $v=$data;foreach(explode('.',$path) as $part){if(!is_array($v)||!array_key_exists($part,$v))return ''; $v=$v[$part];}return $v; }

    private function sanitizeHtml(string $html): string
    {
        $html=preg_replace('#<script\b[^>]*>.*?</script>#is','',$html)??$html;
        $html=preg_replace('/\son\w+\s*=\s*("[^"]*"|\'[^\']*\'|[^\s>]+)/i','',$html)??$html;
        $html=preg_replace('/javascript\s*:/i','',$html)??$html;return $html;
    }
    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?:'document';}
    private function uniqueCode(string $base): string {$c=$base;$i=2;$q=Database::connection()->prepare("SELECT COUNT(*) FROM axiom_document_templates WHERE code=:c");while(true){$q->execute(['c'=>$c]);if((int)$q->fetchColumn()===0)return $c;$c=$base.'_'.$i++;}}
}
