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

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

final class EnrollmentOptimizer
{
    private const DAYS=['lundi','mardi','mercredi','jeudi','vendredi','samedi','dimanche'];

    public static function analyse(int $organisationId): array
    {
        $pdo=Database::connection();
        $year=self::activeYear($pdo,$organisationId);
        if(!$year){
            return ['year'=>null,'suggestions'=>[],'unmatched'=>[],'summary'=>['requests'=>0,'suggestions'=>0,'places'=>0]];
        }

        $requestsQ=$pdo->prepare(
            "SELECT r.id,r.membre_id,r.discipline_id,r.cda_slot_id,r.disponibilites,r.created_at,r.statut,
                    CONCAT(m.prenom,' ',m.nom) membre_nom,d.libelle discipline_libelle
             FROM demandes_inscription r
             INNER JOIN membres_famille m ON m.id=r.membre_id
             INNER JOIN disciplines d ON d.id=r.discipline_id
             WHERE r.organisation_id=?
               AND r.annee_scolaire_id=?
               AND r.statut IN('file_attente','a_etudier')
               AND r.deleted_at IS NULL
               AND NOT EXISTS(
                    SELECT 1 FROM cda_slot_assignment_proposals ap
                    WHERE ap.demande_inscription_id=r.id AND ap.statut='en_attente'
               )
             ORDER BY r.created_at,r.id"
        );
        $requestsQ->execute([$organisationId,(int)$year['id']]);
        $requests=$requestsQ->fetchAll()?:[];

        $slotsQ=$pdo->prepare(
            "SELECT ps.id,ps.professeur_id,ps.discipline_id,ps.jour_semaine,ps.heure_debut,ps.heure_fin,
                    ps.type_cours,ps.capacite_max,CONCAT(p.prenom,' ',p.nom) professeur_nom,
                    s.libelle salle_libelle,
                    LEAST(ps.capacite_max,COALESCE(NULLIF(s.capacite,0),ps.capacite_max)) capacite_effective,
                    (SELECT COUNT(*) FROM cda_slot_enrollments e
                     WHERE e.slot_id=ps.id AND e.statut='active' AND e.deleted_at IS NULL) inscrits,
                    (SELECT COUNT(*) FROM cda_slot_assignment_proposals ap
                     WHERE ap.slot_id=ps.id AND ap.statut='en_attente') propositions
             FROM cda_professor_slots ps
             INNER JOIN professeurs p ON p.id=ps.professeur_id AND p.actif=1 AND p.deleted_at IS NULL
             LEFT JOIN salles s ON s.id=ps.salle_id
             WHERE ps.organisation_id=? AND ps.annee_scolaire_id=?
               AND ps.statut='valide' AND ps.deleted_at IS NULL
             ORDER BY FIELD(ps.jour_semaine,'lundi','mardi','mercredi','jeudi','vendredi','samedi','dimanche'),ps.heure_debut"
        );
        $slotsQ->execute([$organisationId,(int)$year['id']]);
        $slots=$slotsQ->fetchAll()?:[];

        $remaining=[];$totalPlaces=0;
        foreach($slots as &$slot){
            $capacity=max(0,(int)$slot['capacite_effective']);
            $left=max(0,$capacity-(int)$slot['inscrits']-(int)$slot['propositions']);
            $slot['places_disponibles']=$left;
            $remaining[(int)$slot['id']]=$left;
            $totalPlaces+=$left;
        }
        unset($slot);

        $suggestions=[];$unmatched=[];
        foreach($requests as $request){
            $best=null;
            foreach($slots as $slot){
                if((int)$slot['discipline_id']!==(int)$request['discipline_id'])continue;
                if(($remaining[(int)$slot['id']]??0)<1)continue;
                $candidate=self::score($request,$slot);
                if($best===null||$candidate['score']>$best['score']||(
                    $candidate['score']===$best['score'] && (int)$candidate['slot_id']<(int)$best['slot_id']
                ))$best=$candidate;
            }
            if($best===null){$unmatched[]=$request;continue;}
            $remaining[(int)$best['slot_id']]--;
            $suggestions[]=$best;
        }

        return [
            'year'=>$year,
            'suggestions'=>$suggestions,
            'unmatched'=>$unmatched,
            'summary'=>[
                'requests'=>count($requests),
                'suggestions'=>count($suggestions),
                'unmatched'=>count($unmatched),
                'places'=>$totalPlaces,
            ],
        ];
    }

    public static function createProposals(int $organisationId,array $assignments,int $userId): int
    {
        $pdo=Database::connection();$pdo->beginTransaction();$created=0;
        try{
            foreach($assignments as $requestId=>$slotId){
                $requestId=(int)$requestId;$slotId=(int)$slotId;
                if($requestId<1||$slotId<1)continue;
                $requestQ=$pdo->prepare(
                    "SELECT id,discipline_id,statut FROM demandes_inscription
                     WHERE id=? AND organisation_id=? AND deleted_at IS NULL LIMIT 1 FOR UPDATE"
                );
                $requestQ->execute([$requestId,$organisationId]);$request=$requestQ->fetch();
                if(!$request||!in_array((string)$request['statut'],['file_attente','a_etudier'],true))continue;
                $slotQ=$pdo->prepare(
                    "SELECT ps.id,ps.professeur_id,ps.discipline_id,ps.heure_debut,ps.heure_fin,
                            LEAST(ps.capacite_max,COALESCE(NULLIF(s.capacite,0),ps.capacite_max)) capacite_effective,
                            (SELECT COUNT(*) FROM cda_slot_enrollments e WHERE e.slot_id=ps.id AND e.statut='active' AND e.deleted_at IS NULL) inscrits,
                            (SELECT COUNT(*) FROM cda_slot_assignment_proposals ap WHERE ap.slot_id=ps.id AND ap.statut='en_attente') propositions
                     FROM cda_professor_slots ps LEFT JOIN salles s ON s.id=ps.salle_id
                     WHERE ps.id=? AND ps.organisation_id=? AND ps.statut='valide' AND ps.deleted_at IS NULL
                     LIMIT 1 FOR UPDATE"
                );
                $slotQ->execute([$slotId,$organisationId]);$slot=$slotQ->fetch();
                if(!$slot||(int)$slot['discipline_id']!==(int)$request['discipline_id'])continue;
                if((int)$slot['inscrits']+(int)$slot['propositions']>=(int)$slot['capacite_effective'])continue;
                $exists=$pdo->prepare("SELECT COUNT(*) FROM cda_slot_assignment_proposals WHERE demande_inscription_id=? AND statut='en_attente'");
                $exists->execute([$requestId]);if((int)$exists->fetchColumn()>0)continue;
                $pdo->prepare(
                    "INSERT INTO cda_slot_assignment_proposals
                     (organisation_id,professeur_id,slot_id,demande_inscription_id,statut,proposed_by,proposal_comment)
                     VALUES(?,?,?,?, 'en_attente',?,?)"
                )->execute([$organisationId,(int)$slot['professeur_id'],$slotId,$requestId,$userId?:null,'Proposition générée par INSCRIPTION-005 — validation humaine obligatoire.']);
                $pdo->prepare(
                    "UPDATE demandes_inscription SET cda_slot_id=?,statut='creneau_propose',heure_debut_proposee=?,heure_fin_proposee=?,updated_at=NOW()
                     WHERE id=? AND organisation_id=?"
                )->execute([$slotId,$slot['heure_debut'],$slot['heure_fin'],$requestId,$organisationId]);
                $created++;
            }
            $pdo->commit();return $created;
        }catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
    }

    private static function activeYear(\PDO $pdo,int $organisationId): ?array
    {
        $q=$pdo->prepare("SELECT id,libelle,date_debut,date_fin FROM annees_scolaires WHERE organisation_id=? AND active=1 ORDER BY id DESC LIMIT 1");
        $q->execute([$organisationId]);return $q->fetch()?:null;
    }

    private static function score(array $request,array $slot): array
    {
        $availability=self::normalise((string)($request['disponibilites']??''));
        $day=self::normalise((string)$slot['jour_semaine']);
        $time=substr((string)$slot['heure_debut'],0,5);
        $capacity=max(1,(int)$slot['capacite_effective']);
        $used=(int)$slot['inscrits']+(int)$slot['propositions'];
        $score=0;$reasons=[];
        if((int)($request['cda_slot_id']??0)===(int)$slot['id']){$score+=60;$reasons[]='créneau déjà souhaité';}
        if($availability===''){$score+=12;$reasons[]='aucune contrainte déclarée';}
        else{
            if(str_contains($availability,$day)){$score+=25;$reasons[]='jour compatible';}
            if(str_contains($availability,$time)){$score+=20;$reasons[]='horaire compatible';}
            $hour=(int)substr($time,0,2);
            if(($hour<12&&str_contains($availability,'matin'))||($hour>=12&&$hour<18&&str_contains($availability,'apres-midi'))||($hour>=18&&str_contains($availability,'soir'))){$score+=12;$reasons[]='période compatible';}
        }
        $fill=$used/$capacity;
        $balance=(int)round((1-$fill)*25);$score+=$balance;$reasons[]='équilibrage du remplissage';
        $age=max(0,(int)floor((time()-strtotime((string)$request['created_at']))/86400));
        $priority=min(20,$age);$score+=$priority;if($priority>0)$reasons[]='ancienneté de la demande';
        return [
            'request_id'=>(int)$request['id'],'slot_id'=>(int)$slot['id'],'professeur_id'=>(int)$slot['professeur_id'],
            'membre_nom'=>$request['membre_nom'],'discipline_libelle'=>$request['discipline_libelle'],
            'disponibilites'=>$request['disponibilites'],'created_at'=>$request['created_at'],
            'professeur_nom'=>$slot['professeur_nom'],'salle_libelle'=>$slot['salle_libelle'],
            'jour_semaine'=>$slot['jour_semaine'],'heure_debut'=>$slot['heure_debut'],'heure_fin'=>$slot['heure_fin'],
            'capacite_effective'=>$capacity,'occupation'=>$used,'score'=>$score,'reasons'=>$reasons,
        ];
    }

    private static function normalise(string $value): string
    {
        $value=mb_strtolower(trim($value),'UTF-8');
        $ascii=@iconv('UTF-8','ASCII//TRANSLIT//IGNORE',$value);
        return preg_replace('/[^a-z0-9: -]+/',' ',is_string($ascii)?$ascii:$value)??'';
    }
}
