<?php
declare(strict_types=1);
namespace App\Models;

use App\Core\Database;
use RuntimeException;

final class ProfessorRemoteWork
{
    public static function displayMonth(int $organisationId,int $professorId,?string $asked): string
    {
        if($asked&&preg_match('/^\d{4}-\d{2}$/',$asked))return $asked;
        $today=date('Y-m');
        $q=Database::connection()->prepare(
            "SELECT DATE_FORMAT(MIN(date_teletravail),'%Y-%m')
             FROM cda_professor_remote_work
             WHERE organisation_id=? AND professeur_id=? AND deleted_at IS NULL
               AND date_teletravail>=CURDATE()"
        );
        $q->execute([$organisationId,$professorId]);
        return (string)($q->fetchColumn()?:$today);
    }

    public static function all(int $organisationId,int $professorId,string $month): array
    {
        $start=$month.'-01';
        $end=(new \DateTimeImmutable($start))->modify('first day of next month')->format('Y-m-d');
        $q=Database::connection()->prepare(
            "SELECT t.*,CONCAT(p.prenom,' ',p.nom) professeur_nom,
                    CONCAT(c.prenom,' ',c.nom) created_by_name,
                    CONCAT(v.prenom,' ',v.nom) validated_by_name
             FROM cda_professor_remote_work t
             INNER JOIN professeurs p ON p.id=t.professeur_id
             LEFT JOIN utilisateurs c ON c.id=t.created_by
             LEFT JOIN utilisateurs v ON v.id=t.validated_by
             WHERE t.organisation_id=? AND t.professeur_id=? AND t.deleted_at IS NULL
               AND t.date_teletravail>=? AND t.date_teletravail<?
             ORDER BY t.date_teletravail,t.heure_debut"
        );
        $q->execute([$organisationId,$professorId,$start,$end]);
        $items=$q->fetchAll()?:[];
        $closures=SchoolCalendar::closures($organisationId,$start,$end);
        $items=array_values(array_filter($items,static fn(array $item):bool=>
            ($item['mode_teletravail']??'ponctuel')!=='annualise'
            || !SchoolCalendar::isClosed((string)$item['date_teletravail'],$closures)
        ));
        if(!$items)return [];
        $ids=array_map(static fn($row)=>(int)$row['id'],$items);
        $marks=implode(',',array_fill(0,count($ids),'?'));
        $s=Database::connection()->prepare(
            "SELECT remote_work_id,date_teletravail,heure_debut,heure_fin
             FROM cda_professor_remote_work_change_slots
             WHERE remote_work_id IN ($marks) ORDER BY remote_work_id,ordre,id"
        );
        $s->execute($ids);$slots=[];
        foreach($s->fetchAll()?:[] as $slot)$slots[(int)$slot['remote_work_id']][]=$slot;
        foreach($items as &$item)$item['proposed_slots']=$slots[(int)$item['id']]??[];
        unset($item);
        return $items;
    }

    public static function grant(int $organisationId,int $professorId,array $data,int $userId): int
    {
        $allowed=Database::connection()->prepare("SELECT COALESCE(teletravail_autorise,0) FROM professeurs WHERE id=? AND organisation_id=? AND deleted_at IS NULL");
        $allowed->execute([$professorId,$organisationId]);
        if((int)$allowed->fetchColumn()!==1)throw new RuntimeException('Le télétravail n’est pas autorisé dans le profil de ce professeur.');
        $mode=($data['mode_teletravail']??'ponctuel')==='annualise'?'annualise':'ponctuel';
        $date=(string)($data['date_teletravail']??'');
        $periodStart=(string)($data['date_debut']??'');
        $periodEnd=(string)($data['date_fin']??'');
        $weekday=max(1,min(7,(int)($data['jour_semaine']??1)));
        $from=(string)($data['heure_debut']??'');
        $to=(string)($data['heure_fin']??'');
        if(!preg_match('/^\d{2}:\d{2}$/',$from)||!preg_match('/^\d{2}:\d{2}$/',$to)||$to<=$from){
            throw new RuntimeException('Renseignez une plage horaire de télétravail valide.');
        }
        if($mode==='ponctuel'&&!preg_match('/^\d{4}-\d{2}-\d{2}$/',$date))throw new RuntimeException('Renseignez la date du télétravail ponctuel.');
        if($mode==='annualise'&&(!preg_match('/^\d{4}-\d{2}-\d{2}$/',$periodStart)||!preg_match('/^\d{4}-\d{2}-\d{2}$/',$periodEnd)||$periodEnd<$periodStart))throw new RuntimeException('Renseignez une période annualisée valide.');
        $pdo=Database::connection();$dates=[];
        if($mode==='ponctuel'){$dates[]=$date;}else{
            $cursor=new \DateTimeImmutable($periodStart);$last=new \DateTimeImmutable($periodEnd);
            while((int)$cursor->format('N')!==$weekday)$cursor=$cursor->modify('+1 day');
            $closures=SchoolCalendar::closures($organisationId,$periodStart,$periodEnd);
            for(;$cursor<=$last;$cursor=$cursor->modify('+7 days')){
                $value=$cursor->format('Y-m-d');
                if(!SchoolCalendar::isClosed($value,$closures))$dates[]=$value;
            }
        }
        if(!$dates)throw new RuntimeException('Aucune occurrence hors vacances ne correspond à cette période.');
        $series=$mode==='annualise'?bin2hex(random_bytes(16)):null;
        $q=$pdo->prepare(
            "INSERT INTO cda_professor_remote_work
             (organisation_id,professeur_id,date_teletravail,heure_debut,heure_fin,consignes_direction,statut,created_by,mode_teletravail,serie_uuid,date_debut_serie,date_fin_serie,jour_semaine)
             VALUES(?,?,?,?,?,?,'accorde',?,?,?,?,?,?)"
        );
        $pdo->beginTransaction();
        try{
            foreach($dates as $occurrence)$q->execute([$organisationId,$professorId,$occurrence,$from,$to,trim((string)($data['consignes_direction']??''))?:null,$userId?:null,$mode,$series,$mode==='annualise'?$periodStart:null,$mode==='annualise'?$periodEnd:null,$mode==='annualise'?$weekday:null]);
            $pdo->commit();
        }catch(\Throwable $e){$pdo->rollBack();throw $e;}
        return count($dates);
    }

    public static function requestChange(int $organisationId,int $id,array $data,int $userId): int
    {
        $reason=trim((string)($data['motif_modification']??''));
        if($reason==='')throw new RuntimeException('Indiquez le motif de la modification.');
        $dates=(array)($data['slot_date']??[]);$froms=(array)($data['slot_debut']??[]);$tos=(array)($data['slot_fin']??[]);
        $proposed=[];$total=0;
        foreach($dates as $i=>$date){
            $date=(string)$date;$from=(string)($froms[$i]??'');$to=(string)($tos[$i]??'');
            if($date===''&&$from===''&&$to==='')continue;
            if(!preg_match('/^\d{4}-\d{2}-\d{2}$/',$date)||!preg_match('/^\d{2}:\d{2}$/',$from)||!preg_match('/^\d{2}:\d{2}$/',$to)||$to<=$from)throw new RuntimeException('Chaque créneau proposé doit contenir une date et des horaires valides.');
            $total+=(int)((strtotime($date.' '.$to)-strtotime($date.' '.$from))/60);
            $proposed[]=[$date,$from,$to];
        }
        if(!$proposed)throw new RuntimeException('Ajoutez au moins un créneau proposé.');
        $pdo=Database::connection();$pdo->beginTransaction();
        try{
            $find=$pdo->prepare("SELECT * FROM cda_professor_remote_work WHERE id=? AND organisation_id=? AND deleted_at IS NULL AND mode_teletravail='annualise' AND statut IN('accorde','refuse') FOR UPDATE");
            $find->execute([$id,$organisationId]);$item=$find->fetch();
            if(!$item)throw new RuntimeException('Cette occurrence ne peut pas être modifiée.');
            $expected=(int)((strtotime('1970-01-01 '.$item['heure_fin'])-strtotime('1970-01-01 '.$item['heure_debut']))/60);
            if($total!==$expected)throw new RuntimeException('La somme des créneaux proposés doit rester égale à la durée initiale de '.self::minutesLabel($expected).'.');
            $pdo->prepare("DELETE FROM cda_professor_remote_work_change_slots WHERE remote_work_id=?")->execute([$id]);
            $insert=$pdo->prepare("INSERT INTO cda_professor_remote_work_change_slots(remote_work_id,date_teletravail,heure_debut,heure_fin,ordre) VALUES(?,?,?,?,?)");
            foreach($proposed as $i=>$slot)$insert->execute([$id,$slot[0],$slot[1],$slot[2],$i+1]);
            $first=$proposed[0];
            $q=$pdo->prepare(
                "UPDATE cda_professor_remote_work SET changement_statut='demande',
                 nouvelle_date=?,nouvelle_heure_debut=?,nouvelle_heure_fin=?,changement_motif=?,
                 changement_demande_par=?,changement_demande_at=NOW(),changement_reponse_comment=NULL,
                 changement_reponse_at=NULL,updated_at=NOW() WHERE id=?"
            );
            $q->execute([$first[0],$first[1],$first[2],$reason,$userId?:null,$id]);
            $pdo->commit();return (int)$item['professeur_id'];
        }catch(\Throwable $e){$pdo->rollBack();throw $e;}
    }

    public static function decideChange(int $organisationId,int $professorId,int $id,bool $approved,string $comment,int $userId): void
    {
        if(!$approved&&trim($comment)==='')throw new RuntimeException('Indiquez le motif du refus.');
        $pdo=Database::connection();$pdo->beginTransaction();
        try{
            $q=$pdo->prepare("SELECT * FROM cda_professor_remote_work WHERE id=? AND organisation_id=? AND professeur_id=? AND changement_statut='demande' AND deleted_at IS NULL FOR UPDATE");
            $q->execute([$id,$organisationId,$professorId]);$item=$q->fetch();
            if(!$item)throw new RuntimeException('Cette demande de modification a déjà été traitée.');
            if($approved){
                $slots=$pdo->prepare("SELECT * FROM cda_professor_remote_work_change_slots WHERE remote_work_id=? ORDER BY ordre,id");
                $slots->execute([$id]);$proposed=$slots->fetchAll()?:[];
                if(!$proposed)throw new RuntimeException('Les créneaux proposés sont introuvables.');
                $first=array_shift($proposed);
                $u=$pdo->prepare(
                    "UPDATE cda_professor_remote_work SET date_teletravail=?,heure_debut=?,heure_fin=?,
                     changement_statut='accepte',changement_reponse_comment=?,changement_reponse_par=?,
                     changement_reponse_at=NOW(),updated_at=NOW() WHERE id=?"
                );
                $u->execute([$first['date_teletravail'],$first['heure_debut'],$first['heure_fin'],trim($comment)?:null,$userId?:null,$id]);
                $clone=$pdo->prepare(
                    "INSERT INTO cda_professor_remote_work
                     (organisation_id,professeur_id,date_teletravail,heure_debut,heure_fin,consignes_direction,
                      statut,created_by,mode_teletravail,serie_uuid,date_debut_serie,date_fin_serie,jour_semaine,
                      changement_statut,changement_reponse_comment,changement_reponse_par,changement_reponse_at)
                     VALUES(?,?,?,?,?,?,'accorde',?,'annualise',?,?,?,?, 'accepte',?,?,NOW())"
                );
                foreach($proposed as $slot)$clone->execute([
                    $item['organisation_id'],$item['professeur_id'],$slot['date_teletravail'],$slot['heure_debut'],$slot['heure_fin'],
                    $item['consignes_direction'],$item['created_by'],$item['serie_uuid'],$item['date_debut_serie'],
                    $item['date_fin_serie'],(int)date('N',strtotime($slot['date_teletravail'])),trim($comment)?:null,$userId?:null
                ]);
            }else{
                $u=$pdo->prepare(
                    "UPDATE cda_professor_remote_work SET changement_statut='refuse',
                     changement_reponse_comment=?,changement_reponse_par=?,
                     changement_reponse_at=NOW(),updated_at=NOW() WHERE id=?"
                );
            }
            if(!$approved)$u->execute([trim($comment)?:null,$userId?:null,$id]);
            $pdo->prepare("DELETE FROM cda_professor_remote_work_change_slots WHERE remote_work_id=?")->execute([$id]);
            $pdo->commit();
        }catch(\Throwable $e){$pdo->rollBack();throw $e;}
    }

    private static function minutesLabel(int $minutes): string
    {
        $hours=intdiv($minutes,60);$rest=$minutes%60;
        return ($hours?$hours.' h':'').($rest?' '.$rest.' min':'');
    }

    public static function submit(int $organisationId,int $professorId,int $id,string $tasks,bool $adminTest=false): void
    {
        $tasks=trim($tasks);
        if($tasks==='')throw new RuntimeException('Décrivez les tâches effectuées avant l’envoi à la Direction.');
        $dateRule=$adminTest?'':' AND date_teletravail<=CURDATE()';
        $q=Database::connection()->prepare(
            "UPDATE cda_professor_remote_work
             SET taches_effectuees=?,statut='envoye',submitted_at=NOW(),decision_comment=NULL,updated_at=NOW()
             WHERE id=? AND organisation_id=? AND professeur_id=?
               AND statut IN('accorde','refuse') AND deleted_at IS NULL".$dateRule
        );
        $q->execute([$tasks,$id,$organisationId,$professorId]);
        if(!$q->rowCount())throw new RuntimeException($adminTest?'Cette déclaration ne peut plus être modifiée.':'Les tâches peuvent être envoyées à partir du jour prévu pour cette plage de télétravail.');
    }

    public static function decide(int $organisationId,int $id,bool $approved,string $comment,int $userId): int
    {
        if(!$approved&&trim($comment)==='')throw new RuntimeException('Indiquez le motif du refus.');
        $q=Database::connection()->prepare(
            "UPDATE cda_professor_remote_work
             SET statut=?,decision_comment=?,validated_by=?,validated_at=NOW(),updated_at=NOW()
             WHERE id=? AND organisation_id=? AND statut='envoye' AND deleted_at IS NULL"
        );
        $q->execute([$approved?'valide':'refuse',trim($comment)?:null,$userId?:null,$id,$organisationId]);
        if(!$q->rowCount())throw new RuntimeException('Ce compte rendu a déjà été traité.');
        $find=Database::connection()->prepare("SELECT professeur_id FROM cda_professor_remote_work WHERE id=? AND organisation_id=?");
        $find->execute([$id,$organisationId]);
        return (int)$find->fetchColumn();
    }
}
