<?php
declare(strict_types=1);

namespace App\Services;

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

final class StudioDashboardService
{
    private const WIDGET_TYPES = [
        'kpi' => 'Indicateur KPI', 'bar' => 'Graphique en barres', 'line' => 'Courbe',
        'donut' => 'Anneau', 'table' => 'Tableau', 'tasks' => 'Liste de tâches',
        'activity' => 'Activité récente', 'calendar' => 'Calendrier', 'shortcut' => 'Raccourci',
    ];

    private const DATA_SOURCES = [
        'static' => 'Valeur statique', 'entity_count' => 'Nombre d’enregistrements d’une entité',
        'entity_recent' => 'Derniers enregistrements d’une entité', 'workflow_tasks' => 'Tâches workflow ouvertes',
        'workflow_instances' => 'Instances workflow par statut',
    ];

    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 dashboards(int $moduleId): array
    {
        $q=Database::connection()->prepare("SELECT d.*,(SELECT COUNT(*) FROM axiom_dashboard_widgets w WHERE w.dashboard_id=d.id AND w.archived_at IS NULL) widget_count FROM axiom_dashboards d WHERE d.module_id=:m AND d.archived_at IS NULL ORDER BY d.is_default DESC,d.name");
        $q->execute(['m'=>$moduleId]); return $q->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    public function dashboard(int $id): ?array
    {
        $q=Database::connection()->prepare("SELECT * FROM axiom_dashboards WHERE id=:id AND archived_at IS NULL");
        $q->execute(['id'=>$id]); $row=$q->fetch(PDO::FETCH_ASSOC); return $row ?: 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 widgets(int $dashboardId): array
    {
        $q=Database::connection()->prepare("SELECT * FROM axiom_dashboard_widgets WHERE dashboard_id=:d AND archived_at IS NULL ORDER BY position,id");
        $q->execute(['d'=>$dashboardId]); $rows=$q->fetchAll(PDO::FETCH_ASSOC) ?: [];
        foreach($rows as &$row){$row['config']=json_decode((string)($row['config_json']??'{}'),true) ?: [];$row['preview']=$this->previewWidget($row);} unset($row);
        return $rows;
    }

    public function widgetTypes(): array { return self::WIDGET_TYPES; }
    public function dataSources(): array { return self::DATA_SOURCES; }

    public function createDashboard(int $moduleId,array $data,?int $userId): int
    {
        $name=trim((string)($data['name']??'')); if($moduleId<=0||$name==='') throw new InvalidArgumentException('Le module et le nom sont obligatoires.');
        $code=$this->code((string)($data['code']??$name)); $pdo=Database::connection();
        $check=$pdo->prepare("SELECT COUNT(*) FROM axiom_dashboards WHERE module_id=:m AND code=:c AND archived_at IS NULL");$check->execute(['m'=>$moduleId,'c'=>$code]);
        if((int)$check->fetchColumn()>0) throw new InvalidArgumentException('Ce code existe déjà dans le module.');
        $layout=['columns'=>12,'gap'=>16,'refresh_interval'=>max(0,(int)($data['refresh_interval']??0)),'display_mode'=>(string)($data['display_mode']??'cards')];
        $q=$pdo->prepare("INSERT INTO axiom_dashboards(module_id,name,code,description,target_role,status,is_default,layout_json,created_by,updated_by) VALUES(:m,:n,:c,:d,:r,'draft',:def,:layout,:u,:u)");
        $q->execute(['m'=>$moduleId,'n'=>$name,'c'=>$code,'d'=>trim((string)($data['description']??''))?:null,'r'=>trim((string)($data['target_role']??''))?:null,'def'=>!empty($data['is_default'])?1:0,'layout'=>json_encode($layout),'u'=>$userId]);
        return (int)$pdo->lastInsertId();
    }

    public function updateDashboard(int $dashboardId,array $data,?int $userId): void
    {
        $current=$this->dashboard($dashboardId); if(!$current) throw new RuntimeException('Tableau de bord introuvable.');
        $name=trim((string)($data['name']??'')); if($name==='') throw new InvalidArgumentException('Le nom est obligatoire.');
        $layout=json_decode((string)($current['layout_json']??'{}'),true) ?: [];
        $layout['columns']=12; $layout['gap']=max(0,min(48,(int)($data['gap']??16)));
        $layout['refresh_interval']=max(0,min(3600,(int)($data['refresh_interval']??0)));
        $layout['display_mode']=in_array(($data['display_mode']??'cards'),['cards','compact'],true)?$data['display_mode']:'cards';
        $q=Database::connection()->prepare("UPDATE axiom_dashboards SET name=:n,description=:d,target_role=:r,is_default=:def,layout_json=:layout,status='draft',updated_by=:u,updated_at=CURRENT_TIMESTAMP WHERE id=:id AND archived_at IS NULL");
        $q->execute(['n'=>$name,'d'=>trim((string)($data['description']??''))?:null,'r'=>trim((string)($data['target_role']??''))?:null,'def'=>!empty($data['is_default'])?1:0,'layout'=>json_encode($layout,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),'u'=>$userId,'id'=>$dashboardId]);
    }

    public function duplicateDashboard(int $dashboardId,?int $userId): int
    {
        $source=$this->dashboard($dashboardId); if(!$source) throw new RuntimeException('Tableau de bord introuvable.');
        $pdo=Database::connection(); $pdo->beginTransaction();
        try{
            $name=$source['name'].' — copie'; $code=$this->uniqueDashboardCode((int)$source['module_id'],$source['code'].'_copie');
            $q=$pdo->prepare("INSERT INTO axiom_dashboards(module_id,name,code,description,target_role,status,is_default,layout_json,created_by,updated_by) VALUES(:m,:n,:c,:d,:r,'draft',0,:layout,:u,:u)");
            $q->execute(['m'=>$source['module_id'],'n'=>$name,'c'=>$code,'d'=>$source['description'],'r'=>$source['target_role'],'layout'=>$source['layout_json'],'u'=>$userId]);
            $newId=(int)$pdo->lastInsertId();
            $widgets=$this->widgets($dashboardId); $ins=$pdo->prepare("INSERT INTO axiom_dashboard_widgets(dashboard_id,title,code,widget_type,data_source,config_json,width_desktop,width_tablet,width_mobile,position,created_by,updated_by) VALUES(:d,:t,:c,:wt,:ds,:cfg,:wd,:wtab,:wm,:p,:u,:u)");
            foreach($widgets as $w){$ins->execute(['d'=>$newId,'t'=>$w['title'],'c'=>$w['code'],'wt'=>$w['widget_type'],'ds'=>$w['data_source'],'cfg'=>$w['config_json'],'wd'=>$w['width_desktop'],'wtab'=>$w['width_tablet'],'wm'=>$w['width_mobile'],'p'=>$w['position'],'u'=>$userId]);}
            $pdo->commit(); return $newId;
        }catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
    }

    public function addWidget(int $dashboardId,array $data,?int $userId): int
    {
        if(!$this->dashboard($dashboardId)) throw new RuntimeException('Tableau de bord introuvable.');
        $title=trim((string)($data['title']??'')); $type=(string)($data['widget_type']??'kpi'); $source=(string)($data['data_source']??'static');
        if($title===''||!isset(self::WIDGET_TYPES[$type])||!isset(self::DATA_SOURCES[$source])) throw new InvalidArgumentException('Titre, type et source invalides.');
        $position=(int)Database::connection()->query("SELECT COALESCE(MAX(position),0)+1 FROM axiom_dashboard_widgets WHERE dashboard_id=".(int)$dashboardId)->fetchColumn();
        $config=$this->widgetConfig($data); $q=Database::connection()->prepare("INSERT INTO axiom_dashboard_widgets(dashboard_id,title,code,widget_type,data_source,config_json,width_desktop,width_tablet,width_mobile,position,created_by,updated_by) VALUES(:d,:t,:c,:wt,:ds,:cfg,:wd,:wtab,:wm,:p,:u,:u)");
        $q->execute(['d'=>$dashboardId,'t'=>$title,'c'=>$this->uniqueWidgetCode($dashboardId,$this->code((string)($data['code']??$title))),'wt'=>$type,'ds'=>$source,'cfg'=>json_encode($config,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),'wd'=>$this->width($data['width_desktop']??4),'wtab'=>$this->width($data['width_tablet']??6),'wm'=>$this->width($data['width_mobile']??12),'p'=>$position,'u'=>$userId]);
        $this->touch($dashboardId,$userId); return (int)Database::connection()->lastInsertId();
    }

    public function updateWidget(int $dashboardId,int $widgetId,array $data,?int $userId): void
    {
        $title=trim((string)($data['title']??''));$type=(string)($data['widget_type']??'kpi');$source=(string)($data['data_source']??'static');
        if($title===''||!isset(self::WIDGET_TYPES[$type])||!isset(self::DATA_SOURCES[$source])) throw new InvalidArgumentException('Paramètres du widget invalides.');
        $q=Database::connection()->prepare("UPDATE axiom_dashboard_widgets SET title=:t,widget_type=:wt,data_source=:ds,config_json=:cfg,width_desktop=:wd,width_tablet=:wtab,width_mobile=:wm,updated_by=:u,updated_at=CURRENT_TIMESTAMP WHERE id=:id AND dashboard_id=:d AND archived_at IS NULL");
        $q->execute(['t'=>$title,'wt'=>$type,'ds'=>$source,'cfg'=>json_encode($this->widgetConfig($data),JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),'wd'=>$this->width($data['width_desktop']??4),'wtab'=>$this->width($data['width_tablet']??6),'wm'=>$this->width($data['width_mobile']??12),'u'=>$userId,'id'=>$widgetId,'d'=>$dashboardId]);
        $this->touch($dashboardId,$userId);
    }

    public function saveOrder(int $dashboardId,array $ids,?int $userId): void
    {
        $pdo=Database::connection();$pdo->beginTransaction();try{$q=$pdo->prepare("UPDATE axiom_dashboard_widgets SET position=:p,updated_by=:u,updated_at=CURRENT_TIMESTAMP WHERE id=:id AND dashboard_id=:d AND archived_at IS NULL");foreach(array_values($ids) as $i=>$id){$q->execute(['p'=>$i+1,'u'=>$userId,'id'=>(int)$id,'d'=>$dashboardId]);}$pdo->commit();$this->touch($dashboardId,$userId);}catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
    }

    public function publish(int $dashboardId,?int $userId): void
    {
        $dashboard=$this->dashboard($dashboardId);$widgets=$this->widgets($dashboardId);if(!$dashboard||!$widgets) throw new RuntimeException('Ajoutez au moins un widget avant publication.');
        $settings=json_decode((string)($dashboard['layout_json']??'{}'),true) ?: [];$settings['widgets']=[];
        foreach($widgets as $w){$settings['widgets'][]=['id'=>(int)$w['id'],'code'=>$w['code'],'type'=>$w['widget_type'],'desktop'=>(int)$w['width_desktop'],'tablet'=>(int)$w['width_tablet'],'mobile'=>(int)$w['width_mobile'],'position'=>(int)$w['position']];}
        $q=Database::connection()->prepare("UPDATE axiom_dashboards SET status='published',published_at=CURRENT_TIMESTAMP,layout_json=:j,updated_by=:u,updated_at=CURRENT_TIMESTAMP WHERE id=:id AND archived_at IS NULL");$q->execute(['j'=>json_encode($settings,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),'u'=>$userId,'id'=>$dashboardId]);
    }

    public function archive(string $type,int $id,?int $userId): void
    {
        $table=$type==='dashboard'?'axiom_dashboards':($type==='widget'?'axiom_dashboard_widgets':'');if($table==='') throw new InvalidArgumentException('Type invalide.');
        Database::connection()->prepare("UPDATE {$table} SET archived_at=CURRENT_TIMESTAMP,updated_by=:u,updated_at=CURRENT_TIMESTAMP WHERE id=:id")->execute(['u'=>$userId,'id'=>$id]);
    }

    private function widgetConfig(array $data): array
    {
        return ['entity_id'=>(int)($data['entity_id']??0),'static_value'=>trim((string)($data['static_value']??'')),'icon'=>trim((string)($data['icon']??'')),'link_url'=>trim((string)($data['link_url']??'')),'limit'=>max(1,min(50,(int)($data['limit']??5))),'subtitle'=>trim((string)($data['subtitle']??'')),'empty_message'=>trim((string)($data['empty_message']??'Aucune donnée'))];
    }

    private function previewWidget(array $widget): array
    {
        $config=json_decode((string)($widget['config_json']??'{}'),true) ?: [];$source=(string)$widget['data_source'];
        try{
            if($source==='static') return ['kind'=>'value','value'=>($config['static_value']??'')!==''?$config['static_value']:'—'];
            if($source==='workflow_tasks') return ['kind'=>'value','value'=>(int)Database::connection()->query("SELECT COUNT(*) FROM axiom_workflow_tasks WHERE status IN ('open','pending')")->fetchColumn()];
            if($source==='workflow_instances') return ['kind'=>'series','rows'=>Database::connection()->query("SELECT status label,COUNT(*) value FROM axiom_workflow_instances GROUP BY status ORDER BY value DESC")->fetchAll(PDO::FETCH_ASSOC)?:[]];
            $entityId=(int)($config['entity_id']??0);$q=Database::connection()->prepare("SELECT table_name FROM axiom_studio_entities WHERE id=:id AND archived_at IS NULL");$q->execute(['id'=>$entityId]);$table=(string)$q->fetchColumn();
            if($table===''||!preg_match('/^[a-zA-Z0-9_]+$/',$table)) return ['kind'=>'message','value'=>'Entité non configurée'];
            if($source==='entity_count') return ['kind'=>'value','value'=>(int)Database::connection()->query("SELECT COUNT(*) FROM `{$table}`")->fetchColumn()];
            if($source==='entity_recent'){$limit=max(1,min(20,(int)($config['limit']??5)));return ['kind'=>'table','rows'=>Database::connection()->query("SELECT * FROM `{$table}` ORDER BY id DESC LIMIT {$limit}")->fetchAll(PDO::FETCH_ASSOC)?:[]];}
        }catch(Throwable){return ['kind'=>'message','value'=>'Aperçu indisponible'];}
        return ['kind'=>'message','value'=>'Source prête'];
    }

    private function touch(int $id,?int $userId): void {Database::connection()->prepare("UPDATE axiom_dashboards SET status='draft',updated_by=:u,updated_at=CURRENT_TIMESTAMP WHERE id=:id")->execute(['u'=>$userId,'id'=>$id]);}
    private function width(mixed $v): int{return max(1,min(12,(int)$v));}
    private function uniqueDashboardCode(int $moduleId,string $base): string{$code=$this->code($base);$i=2;$q=Database::connection()->prepare("SELECT COUNT(*) FROM axiom_dashboards WHERE module_id=:m AND code=:c AND archived_at IS NULL");while(true){$q->execute(['m'=>$moduleId,'c'=>$code]);if((int)$q->fetchColumn()===0)return $code;$code=substr($this->code($base),0,90).'_'.$i++;}}
    private function uniqueWidgetCode(int $dashboardId,string $base): string{$code=$base;$i=2;$q=Database::connection()->prepare("SELECT COUNT(*) FROM axiom_dashboard_widgets WHERE dashboard_id=:d AND code=:c AND archived_at IS NULL");while(true){$q->execute(['d'=>$dashboardId,'c'=>$code]);if((int)$q->fetchColumn()===0)return $code;$code=substr($base,0,90).'_'.$i++;}}
    private function code(string $value): string{$value=strtolower(trim($value));$value=preg_replace('/[^a-z0-9]+/','_',$value)??'';$value=trim($value,'_');if($value===''||preg_match('/^[0-9]/',$value))$value='dashboard_'.$value;return substr($value,0,100);}
}
