<?php
declare(strict_types=1);

namespace App\Documents;

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

final class DocumentRepository
{
    private PDO $pdo;
    private string $storageRoot;

    public function __construct(?string $storageRoot = null)
    {
        $this->pdo = Database::connection();
        $this->storageRoot = $storageRoot ?: dirname(__DIR__, 2) . '/storage/axiom_documents';
    }

    public function dashboard(): array
    {
        return [
            'documents' => (int)$this->pdo->query("SELECT COUNT(*) FROM axiom_documents WHERE archived_at IS NULL")->fetchColumn(),
            'versions' => (int)$this->pdo->query("SELECT COUNT(*) FROM axiom_document_versions")->fetchColumn(),
            'signatures' => (int)$this->pdo->query("SELECT COUNT(*) FROM axiom_document_signatures WHERE status <> 'cancelled'")->fetchColumn(),
            'templates' => (int)$this->pdo->query("SELECT COUNT(*) FROM axiom_document_templates WHERE archived_at IS NULL")->fetchColumn(),
        ];
    }

    public function categories(): array
    {
        return $this->pdo->query("SELECT * FROM axiom_document_categories WHERE archived_at IS NULL ORDER BY sort_order,name")->fetchAll() ?: [];
    }

    public function objects(): array
    {
        return $this->pdo->query("SELECT uuid,reference,label,status FROM axiom_objects WHERE archived_at IS NULL ORDER BY updated_at DESC LIMIT 500")->fetchAll() ?: [];
    }

    public function documents(string $query = '', int $categoryId = 0): array
    {
        $where = ['d.archived_at IS NULL']; $args = [];
        if ($query !== '') { $where[] = '(d.title LIKE ? OR d.reference LIKE ? OR d.description LIKE ? OR v.original_name LIKE ?)'; $like='%'.$query.'%'; array_push($args,$like,$like,$like,$like); }
        if ($categoryId > 0) { $where[] = 'd.category_id=?'; $args[]=$categoryId; }
        $sql = "SELECT d.*,c.name category_name,v.id version_id,v.version_no,v.original_name,v.mime_type,v.size_bytes,v.created_at version_created_at,
                (SELECT COUNT(*) FROM axiom_document_versions x WHERE x.document_id=d.id) version_count,
                (SELECT COUNT(*) FROM axiom_document_links l WHERE l.document_id=d.id) link_count
                FROM axiom_documents d
                LEFT JOIN axiom_document_categories c ON c.id=d.category_id
                LEFT JOIN axiom_document_versions v ON v.id=d.current_version_id
                WHERE ".implode(' AND ',$where)." ORDER BY d.updated_at DESC LIMIT 300";
        $s=$this->pdo->prepare($sql); $s->execute($args); return $s->fetchAll() ?: [];
    }

    public function document(int $id): ?array
    {
        $s=$this->pdo->prepare("SELECT d.*,c.name category_name FROM axiom_documents d LEFT JOIN axiom_document_categories c ON c.id=d.category_id WHERE d.id=?");
        $s->execute([$id]); return $s->fetch() ?: null;
    }

    public function versions(int $documentId): array
    {
        $s=$this->pdo->prepare('SELECT * FROM axiom_document_versions WHERE document_id=? ORDER BY version_no DESC'); $s->execute([$documentId]); return $s->fetchAll() ?: [];
    }

    public function links(int $documentId): array
    {
        $s=$this->pdo->prepare("SELECT l.*,o.reference object_reference,o.label object_label FROM axiom_document_links l JOIN axiom_objects o ON o.uuid=l.object_uuid WHERE l.document_id=? ORDER BY l.created_at DESC"); $s->execute([$documentId]); return $s->fetchAll() ?: [];
    }

    public function signatures(int $documentId): array
    {
        $s=$this->pdo->prepare('SELECT * FROM axiom_document_signatures WHERE document_id=? ORDER BY created_at DESC'); $s->execute([$documentId]); return $s->fetchAll() ?: [];
    }

    public function templates(): array
    {
        return $this->pdo->query("SELECT * FROM axiom_document_templates WHERE archived_at IS NULL ORDER BY name")->fetchAll() ?: [];
    }

    public function saveCategory(array $input): int
    {
        $code=$this->code((string)($input['code']??'')); $name=trim((string)($input['name']??''));
        if($code===''||$name==='') throw new InvalidArgumentException('Le code et le nom de la catégorie sont obligatoires.');
        $s=$this->pdo->prepare('INSERT INTO axiom_document_categories(code,name,description,sort_order) VALUES(?,?,?,?) ON DUPLICATE KEY UPDATE name=VALUES(name),description=VALUES(description),sort_order=VALUES(sort_order),archived_at=NULL');
        $s->execute([$code,$name,trim((string)($input['description']??'')),max(0,(int)($input['sort_order']??0))]);
        return $this->resolveId('axiom_document_categories',$code);
    }

    public function upload(array $input, array $file, ?int $userId): int
    {
        $title=trim((string)($input['title']??'')); if($title==='') throw new InvalidArgumentException('Le titre est obligatoire.');
        $documentUuid=$this->uuid();
        $dir=$this->storageRoot.'/'.date('Y').'/'.date('m').'/'.$documentUuid;
        $storedFile=SecureFileService::storeUploaded($file,$dir,[
            'application/pdf'=>['pdf'],'image/jpeg'=>['jpg','jpeg'],'image/png'=>['png'],'image/webp'=>['webp'],
            'text/plain'=>['txt'],'text/csv'=>['csv'],'application/msword'=>['doc'],
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document'=>['docx'],
            'application/vnd.ms-excel'=>['xls'],'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'=>['xlsx'],
        ],25*1024*1024,'v1');
        $size=(int)$storedFile['size'];$mime=$storedFile['mime'];$original=$storedFile['original'];
        $reference=$this->reference();
        $this->pdo->beginTransaction();
        try {
            $s=$this->pdo->prepare('INSERT INTO axiom_documents(uuid,reference,title,description,category_id,status,created_by,updated_by) VALUES(?,?,?,?,?,\'active\',?,?)');
            $s->execute([$documentUuid,$reference,$title,trim((string)($input['description']??'')),($input['category_id']??'')!==''?(int)$input['category_id']:null,$userId,$userId]);
            $id=(int)$this->pdo->lastInsertId();
            $target=$storedFile['path'];
            $relative=date('Y').'/'.date('m').'/'.$documentUuid.'/'.$storedFile['name'];
            $hash=$storedFile['sha256'];
            $s=$this->pdo->prepare('INSERT INTO axiom_document_versions(document_id,version_no,original_name,stored_path,mime_type,size_bytes,sha256,notes,created_by) VALUES(?,1,?,?,?,?,?,?,?)');
            $s->execute([$id,$original,$relative,$mime,$size,$hash,trim((string)($input['notes']??'')),$userId]); $versionId=(int)$this->pdo->lastInsertId();
            $this->pdo->prepare('UPDATE axiom_documents SET current_version_id=? WHERE id=?')->execute([$versionId,$id]);
            if(!empty($input['object_uuid'])) $this->link($id,(string)$input['object_uuid'],'attachment',$userId,false);
            $this->objectEvent((string)($input['object_uuid']??''),'document.added','Document ajouté',$userId,['document_id'=>$id,'reference'=>$reference]);
            $this->pdo->commit(); return $id;
        } catch(Throwable $e) { if($this->pdo->inTransaction())$this->pdo->rollBack(); throw $e; }
    }

    public function addVersion(int $documentId, array $file, string $notes, ?int $userId): int
    {
        $doc=$this->document($documentId); if(!$doc) throw new InvalidArgumentException('Document introuvable.');
        $q=$this->pdo->prepare('SELECT COALESCE(MAX(version_no),0)+1 FROM axiom_document_versions WHERE document_id=?');$q->execute([$documentId]);$version=(int)$q->fetchColumn();
        $dir=$this->storageRoot.'/'.date('Y').'/'.date('m').'/'.$doc['uuid'];
        $storedFile=SecureFileService::storeUploaded($file,$dir,[
            'application/pdf'=>['pdf'],'image/jpeg'=>['jpg','jpeg'],'image/png'=>['png'],'image/webp'=>['webp'],
            'text/plain'=>['txt'],'text/csv'=>['csv'],'application/msword'=>['doc'],
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document'=>['docx'],
            'application/vnd.ms-excel'=>['xls'],'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'=>['xlsx'],
        ],25*1024*1024,'v'.$version);
        $mime=$storedFile['mime'];$original=$storedFile['original'];$size=(int)$storedFile['size'];
        $target=$storedFile['path'];$relative=date('Y').'/'.date('m').'/'.$doc['uuid'].'/'.$storedFile['name'];
        $this->pdo->beginTransaction();
        try{$s=$this->pdo->prepare('INSERT INTO axiom_document_versions(document_id,version_no,original_name,stored_path,mime_type,size_bytes,sha256,notes,created_by) VALUES(?,?,?,?,?,?,?,?,?)');$s->execute([$documentId,$version,$original,$relative,$mime,$size,$storedFile['sha256'],$notes,$userId]);$vid=(int)$this->pdo->lastInsertId();$this->pdo->prepare('UPDATE axiom_documents SET current_version_id=?,updated_by=?,updated_at=NOW() WHERE id=?')->execute([$vid,$userId,$documentId]);$this->pdo->commit();return $vid;}catch(Throwable $e){if($this->pdo->inTransaction())$this->pdo->rollBack();throw $e;}
    }

    public function link(int $documentId,string $objectUuid,string $role,?int $userId,bool $validate=true):void
    {
        if($validate && (!$this->document($documentId)||$objectUuid==='')) throw new InvalidArgumentException('Document ou objet invalide.');
        $s=$this->pdo->prepare('INSERT INTO axiom_document_links(document_id,object_uuid,link_role,created_by) VALUES(?,?,?,?) ON DUPLICATE KEY UPDATE link_role=VALUES(link_role)');$s->execute([$documentId,$objectUuid,$role?:'attachment',$userId]);
    }

    public function requestSignature(array $input,?int $userId):int
    {
        $documentId=max(0,(int)($input['document_id']??0));$name=trim((string)($input['signer_name']??''));$email=trim((string)($input['signer_email']??''));
        if(!$this->document($documentId)||$name==='') throw new InvalidArgumentException('Document et signataire obligatoires.');
        if($email!==''&&!filter_var($email,FILTER_VALIDATE_EMAIL)) throw new InvalidArgumentException('Adresse e-mail invalide.');
        $token=bin2hex(random_bytes(32));$s=$this->pdo->prepare('INSERT INTO axiom_document_signatures(document_id,signer_name,signer_email,signature_type,status,token_hash,expires_at,created_by) VALUES(?,?,?,?,\'requested\',?,?,?)');
        $expiry=date('Y-m-d H:i:s',strtotime('+'.max(1,(int)($input['expires_days']??30)).' days'));$s->execute([$documentId,$name,$email?:null,trim((string)($input['signature_type']??'approval')),hash('sha256',$token),$expiry,$userId]);return (int)$this->pdo->lastInsertId();
    }

    public function markSigned(int $signatureId,?int $userId):void
    {
        $s=$this->pdo->prepare("UPDATE axiom_document_signatures SET status='signed',signed_at=NOW(),signed_by_user_id=?,updated_at=NOW() WHERE id=? AND status='requested'");$s->execute([$userId,$signatureId]);if($s->rowCount()===0)throw new InvalidArgumentException('Demande de signature introuvable ou déjà traitée.');
    }

    public function saveTemplate(array $input,?int $userId):int
    {
        $code=$this->code((string)($input['code']??''));$name=trim((string)($input['name']??''));$body=(string)($input['body_html']??'');
        if($code===''||$name===''||trim($body)==='') throw new InvalidArgumentException('Code, nom et contenu du modèle obligatoires.');
        $s=$this->pdo->prepare('INSERT INTO axiom_document_templates(code,name,description,output_format,body_html,version_no,active,created_by,updated_by) VALUES(?,?,?,?,?,1,1,?,?) ON DUPLICATE KEY UPDATE name=VALUES(name),description=VALUES(description),output_format=VALUES(output_format),body_html=VALUES(body_html),version_no=version_no+1,active=1,archived_at=NULL,updated_by=VALUES(updated_by),updated_at=NOW()');$s->execute([$code,$name,trim((string)($input['description']??'')),trim((string)($input['output_format']??'pdf')),$body,$userId,$userId]);return $this->resolveId('axiom_document_templates',$code);
    }

    public function archive(int $documentId,?int $userId):void
    {
        $s=$this->pdo->prepare("UPDATE axiom_documents SET status='archived',archived_at=NOW(),updated_by=?,updated_at=NOW() WHERE id=? AND archived_at IS NULL");$s->execute([$userId,$documentId]);if($s->rowCount()===0)throw new InvalidArgumentException('Document introuvable ou déjà archivé.');
    }

    public function versionForDownload(int $versionId): ?array
    {
        $s=$this->pdo->prepare('SELECT v.*,d.title,d.reference FROM axiom_document_versions v JOIN axiom_documents d ON d.id=v.document_id WHERE v.id=? AND d.archived_at IS NULL');$s->execute([$versionId]);$row=$s->fetch();if(!$row)return null;$row['absolute_path']=$this->storageRoot.'/'.$row['stored_path'];return $row;
    }

    private function resolveId(string $table,string $code):int{$q=$this->pdo->prepare("SELECT id FROM {$table} WHERE code=?");$q->execute([$code]);return (int)$q->fetchColumn();}
    private function code(string $value):string{$v=strtolower(trim($value));$v=preg_replace('/[^a-z0-9._-]+/','-',$v)??'';return trim($v,'-');}
    private function reference():string{return 'DOC-'.date('Ymd').'-'.strtoupper(substr(bin2hex(random_bytes(5)),0,10));}
    private function uuid():string{$d=random_bytes(16);$d[6]=chr((ord($d[6])&0x0f)|0x40);$d[8]=chr((ord($d[8])&0x3f)|0x80);$h=bin2hex($d);return substr($h,0,8).'-'.substr($h,8,4).'-'.substr($h,12,4).'-'.substr($h,16,4).'-'.substr($h,20);}
    private function objectEvent(string $uuid,string $type,string $label,?int $userId,array $data):void{if($uuid==='')return;$s=$this->pdo->prepare('INSERT INTO axiom_object_events(object_uuid,event_type,label,data_json,actor_user_id) VALUES(?,?,?,?,?)');$s->execute([$uuid,$type,$label,json_encode($data,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),$userId]);}
}
