<?php
declare(strict_types=1);
namespace App\Services;
use App\Core\Database;
use PDO;
use RuntimeException;
final class PortalAccountService{
 public function provisionFamilyAccount(PDO $pdo,int $org,int $clientId,int $familyId,string $nom,string $prenom,string $email,int $actor):array{
  $email=mb_strtolower(trim($email),'UTF-8');if(!filter_var($email,FILTER_VALIDATE_EMAIL))throw new RuntimeException('Une adresse e-mail valide est obligatoire pour créer l’espace Famille.');
  $q=$pdo->prepare('SELECT id FROM utilisateurs WHERE email=:email AND deleted_at IS NULL LIMIT 1');$q->execute(['email'=>$email]);if((int)$q->fetchColumn()>0)throw new RuntimeException('Cette adresse e-mail est déjà utilisée. Le rattachement doit être réalisé depuis le Centre des comptes portail.');
  $q=$pdo->prepare("INSERT INTO utilisateurs(organisation_id,nom,prenom,email,mot_de_passe,statut,actif,security_version,created_at,updated_at) VALUES(:o,:n,:p,:e,:m,'inactif',0,1,NOW(),NOW())");$q->execute(['o'=>$org,'n'=>$nom,'p'=>$prenom,'e'=>$email,'m'=>password_hash(bin2hex(random_bytes(32)),PASSWORD_DEFAULT)]);$uid=(int)$pdo->lastInsertId();
  $q=$pdo->prepare("SELECT id FROM roles WHERE organisation_id=:o AND code='famille' AND actif=1 AND deleted_at IS NULL LIMIT 1");$q->execute(['o'=>$org]);$rid=(int)$q->fetchColumn();if($rid<=0)throw new RuntimeException('Le rôle portail Famille n’est pas installé.');
  $pdo->prepare('INSERT INTO utilisateur_roles(utilisateur_id,role_id) VALUES(:u,:r)')->execute(['u'=>$uid,'r'=>$rid]);
  $pdo->prepare('INSERT INTO cda_family_accounts(utilisateur_id,client_id,famille_id,principal) VALUES(:u,:c,:f,1)')->execute(['u'=>$uid,'c'=>$clientId,'f'=>$familyId]);
  $q=$pdo->prepare("INSERT INTO cda_portal_accounts(organisation_id,utilisateur_id,client_id,famille_id,account_type,status,password_reset_required,created_by) VALUES(:o,:u,:c,:f,'family','invited',1,:a)");$q->execute(['o'=>$org,'u'=>$uid,'c'=>$clientId,'f'=>$familyId,'a'=>$actor]);$aid=(int)$pdo->lastInsertId();
  $token=$this->createToken($pdo,$aid,'invite',$actor);$this->audit($pdo,$aid,$actor,'account_created',['email'=>$this->maskEmail($email)]);return ['account_id'=>$aid,'user_id'=>$uid,'email'=>$email,'token'=>$token];
 }
 public function sendInvitation(array $inv):bool{$email=filter_var((string)($inv['email']??''),FILTER_VALIDATE_EMAIL);$token=(string)($inv['token']??'');if(!$email||$token==='')return false;$host=preg_replace('/[^A-Za-z0-9.:-]/','',(string)($_SERVER['HTTP_HOST']??'localhost'));$scheme=(!empty($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off')?'https':'http';$url=$scheme.'://'.$host.'/public/portail/activation?token='.rawurlencode($token);$body='<h2>Carrefour des Arts</h2><p>Votre espace Famille a été créé.</p><p><a href="'.htmlspecialchars($url,ENT_QUOTES,'UTF-8').'">Définir mon mot de passe</a></p><p>Ce lien personnel expire dans 48 heures.</p>';$ok=@mail((string)$email,'Activation de votre espace Famille — Carrefour des Arts',$body,"MIME-Version: 1.0\r\nContent-Type: text/html; charset=UTF-8\r\n");$pdo=Database::connection();$pdo->prepare('UPDATE cda_portal_accounts SET invitation_sent_at=IF(:s=1,NOW(),invitation_sent_at),updated_at=NOW() WHERE id=:id')->execute(['s'=>$ok?1:0,'id'=>(int)$inv['account_id']]);$this->audit($pdo,(int)$inv['account_id'],null,$ok?'invitation_sent':'invitation_failed',[]);return $ok;}
 public function accounts(string $query=''):array{$pdo=Database::connection();if($query===''){return $pdo->query("SELECT pa.*,u.nom,u.prenom,u.email,u.derniere_connexion_at,c.code_client,f.reference_famille FROM cda_portal_accounts pa INNER JOIN utilisateurs u ON u.id=pa.utilisateur_id LEFT JOIN clients c ON c.id=pa.client_id LEFT JOIN familles f ON f.id=pa.famille_id ORDER BY pa.created_at DESC,pa.id DESC LIMIT 300")->fetchAll()?:[];}$q=$pdo->prepare("SELECT pa.*,u.nom,u.prenom,u.email,u.derniere_connexion_at,c.code_client,f.reference_famille FROM cda_portal_accounts pa INNER JOIN utilisateurs u ON u.id=pa.utilisateur_id LEFT JOIN clients c ON c.id=pa.client_id LEFT JOIN familles f ON f.id=pa.famille_id WHERE u.nom LIKE :q OR u.prenom LIKE :q OR u.email LIKE :q OR c.code_client LIKE :q OR f.reference_famille LIKE :q ORDER BY pa.created_at DESC,pa.id DESC LIMIT 300");$q->execute(['q'=>'%'.$query.'%']);return $q->fetchAll()?:[];}
 public function resend(int $aid,int $actor):bool{$pdo=Database::connection();$q=$pdo->prepare('SELECT pa.id account_id,u.email FROM cda_portal_accounts pa INNER JOIN utilisateurs u ON u.id=pa.utilisateur_id WHERE pa.id=:id LIMIT 1');$q->execute(['id'=>$aid]);$r=$q->fetch();if(!$r)throw new RuntimeException('Compte portail introuvable.');$r['token']=$this->createToken($pdo,$aid,'reset',$actor);return $this->sendInvitation($r);}
 public function changeStatus(int $aid,string $status,int $actor):void{if(!in_array($status,['active','suspended','archived'],true))throw new RuntimeException('Statut portail invalide.');$pdo=Database::connection();$pdo->beginTransaction();try{$q=$pdo->prepare('SELECT utilisateur_id FROM cda_portal_accounts WHERE id=:id FOR UPDATE');$q->execute(['id'=>$aid]);$uid=(int)$q->fetchColumn();if($uid<=0)throw new RuntimeException('Compte portail introuvable.');$pdo->prepare('UPDATE cda_portal_accounts SET status=:s,updated_at=NOW() WHERE id=:id')->execute(['s'=>$status,'id'=>$aid]);$active=$status==='active'?1:0;$us=$status==='active'?'actif':($status==='suspended'?'bloque':'inactif');$pdo->prepare('UPDATE utilisateurs SET statut=:s,actif=:a,security_version=security_version+1,updated_at=NOW() WHERE id=:id')->execute(['s'=>$us,'a'=>$active,'id'=>$uid]);$this->audit($pdo,$aid,$actor,'status_changed',['status'=>$status]);$pdo->commit();}catch(\Throwable $e){$pdo->rollBack();throw $e;}}
 public function validateToken(string $token):?array{if($token==='')return null;$q=Database::connection()->prepare("SELECT t.id token_id,t.account_id,t.purpose,t.expires_at,pa.utilisateur_id,pa.status,u.email,u.nom,u.prenom FROM cda_portal_tokens t INNER JOIN cda_portal_accounts pa ON pa.id=t.account_id INNER JOIN utilisateurs u ON u.id=pa.utilisateur_id WHERE t.token_hash=:h AND t.used_at IS NULL AND t.expires_at>NOW() LIMIT 1");$q->execute(['h'=>hash('sha256',$token)]);return $q->fetch()?:null;}
 public function activate(string $token,string $password):void{if(strlen($password)<12)throw new RuntimeException('Le mot de passe doit contenir au moins 12 caractères.');$row=$this->validateToken($token);if(!$row)throw new RuntimeException('Ce lien est invalide, expiré ou déjà utilisé.');$pdo=Database::connection();$pdo->beginTransaction();try{$pdo->prepare("UPDATE utilisateurs SET mot_de_passe=:p,statut='actif',actif=1,password_changed_at=NOW(),security_version=security_version+1,updated_at=NOW() WHERE id=:id")->execute(['p'=>password_hash($password,PASSWORD_DEFAULT),'id'=>(int)$row['utilisateur_id']]);$pdo->prepare("UPDATE cda_portal_accounts SET status='active',activated_at=COALESCE(activated_at,NOW()),password_reset_required=0,updated_at=NOW() WHERE id=:id")->execute(['id'=>(int)$row['account_id']]);$pdo->prepare('UPDATE cda_portal_tokens SET used_at=NOW() WHERE account_id=:a AND used_at IS NULL')->execute(['a'=>(int)$row['account_id']]);$this->audit($pdo,(int)$row['account_id'],null,'password_activated',[]);$pdo->commit();}catch(\Throwable $e){$pdo->rollBack();throw $e;}}
 private function createToken(PDO $pdo,int $aid,string $purpose,?int $actor):string{$pdo->prepare('UPDATE cda_portal_tokens SET used_at=NOW() WHERE account_id=:a AND purpose=:p AND used_at IS NULL')->execute(['a'=>$aid,'p'=>$purpose]);$plain=rtrim(strtr(base64_encode(random_bytes(48)),'+/','-_'),'=');$pdo->prepare('INSERT INTO cda_portal_tokens(account_id,purpose,token_hash,expires_at,created_by) VALUES(:a,:p,:h,DATE_ADD(NOW(),INTERVAL 48 HOUR),:c)')->execute(['a'=>$aid,'p'=>$purpose,'h'=>hash('sha256',$plain),'c'=>$actor]);return $plain;}
 private function audit(PDO $pdo,int $aid,?int $actor,string $event,array $details):void{$pdo->prepare('INSERT INTO cda_portal_account_audit(account_id,actor_user_id,event_type,details_json) VALUES(:a,:u,:e,:d)')->execute(['a'=>$aid,'u'=>$actor,'e'=>$event,'d'=>json_encode($details,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)]);}
 private function maskEmail(string $email):string{[$l,$d]=array_pad(explode('@',$email,2),2,'');return mb_substr($l,0,1).'***@'.$d;}
}
