<?php

declare(strict_types=1);

namespace App\Services;

use App\Repositories\SessionRepository;
use App\Repositories\AppointmentRepository;
use InvalidArgumentException;
use RuntimeException;

final class SessionService
{
    public function __construct(private ?SessionRepository $sessions=null, private ?AppointmentRepository $appointments=null)
    {
        $this->sessions ??= new SessionRepository();
        $this->appointments ??= new AppointmentRepository();
    }

    public function list(array $filters, ?int $therapistId=null): array
    {
        return $this->sessions->paginate($filters, $therapistId, 25);
    }

    public function find(int $id): array
    {
        $session=$this->sessions->find($id);
        if ($session===null) throw new RuntimeException('Therapy session not found.',404);
        return $session;
    }

    public function formData(): array
    {
        return ['patients'=>$this->sessions->patients(),'therapists'=>$this->sessions->therapists(),'services'=>$this->sessions->services()];
    }

    public function therapistFormData(int $therapistId): array
    {
        $all=$this->sessions->patients();
        $patients=[];
        foreach ($all as $patient) {
            if ($this->sessions->activeAssignment((int)$patient['id'],$therapistId)!==null) $patients[]=$patient;
        }
        $therapists=[];
        foreach ($this->sessions->therapists() as $therapist) if ((int)$therapist['id']===$therapistId) $therapists[]=$therapist;
        return ['patients'=>$patients,'therapists'=>$therapists,'services'=>$this->sessions->services()];
    }

    public function create(array $data, int $actorId, ?int $forcedTherapistId=null): int
    {
        $normalized=$this->validateAndNormalize($data,$forcedTherapistId);
        $id=$this->sessions->create($normalized);
        if (!empty($normalized['appointment_id'])) {
            $this->appointments->markCompleted((int)$normalized['appointment_id'],$id);
        }
        $this->sessions->audit($actorId,'therapy_session_created',$id,['patient_id'=>$normalized['patient_id'],'therapist_id'=>$normalized['therapist_id'],'billable'=>$normalized['billable']]);
        return $id;
    }

    public function update(int $id, array $data, int $actorId, ?int $forcedTherapistId=null): void
    {
        $this->find($id);
        $normalized=$this->validateAndNormalize($data,$forcedTherapistId);
        $this->sessions->update($id,$normalized);
        $this->sessions->audit($actorId,'therapy_session_updated',$id,['patient_id'=>$normalized['patient_id'],'therapist_id'=>$normalized['therapist_id'],'status'=>$normalized['status']]);
    }

    public function billableSessions(int $patientId): array
    {
        return $this->sessions->billableSessions($patientId);
    }

    public function canTherapistAccess(int $id,int $therapistId): bool
    {
        $session=$this->find($id);
        return (int)$session['therapist_id']===$therapistId && $this->sessions->activeAssignment((int)$session['patient_id'],$therapistId)!==null;
    }

    private function validateAndNormalize(array $d, ?int $forcedTherapistId=null): array
    {
        $patientId=(int)($d['patient_id']??0);
        $therapistId=$forcedTherapistId ?? (int)($d['therapist_id']??0);
        if ($patientId<1) throw new InvalidArgumentException('Select a patient.');
        if ($therapistId<1) throw new InvalidArgumentException('Select a therapist.');
        $date=trim((string)($d['session_date']??''));
        if (!preg_match('/^\d{4}-\d{2}-\d{2}$/',$date)) throw new InvalidArgumentException('Enter a valid session date.');
        $assignment=$this->sessions->activeAssignment($patientId,$therapistId);
        if ($assignment===null) throw new InvalidArgumentException('The therapist must have a current active assignment to this patient before a session can be recorded.');
        if ($date < $assignment['assigned_at']) throw new InvalidArgumentException('Session date cannot be before the patient assignment start date.');
        $start=trim((string)($d['start_time']??''));
        $end=trim((string)($d['end_time']??''));
        if ($start!=='' && !preg_match('/^\d{2}:\d{2}$/',$start)) throw new InvalidArgumentException('Enter a valid start time.');
        if ($end!=='' && !preg_match('/^\d{2}:\d{2}$/',$end)) throw new InvalidArgumentException('Enter a valid end time.');
        if ($start!=='' && $end!=='' && $end <= $start) throw new InvalidArgumentException('End time must be after start time.');
        $duration=null;
        if ($start!=='' && $end!=='') {
            $duration=(int)round((strtotime($date.' '.$end)-strtotime($date.' '.$start))/60);
            if ($duration<1) throw new InvalidArgumentException('Session duration must be at least one minute.');
        }
        $status=(string)($d['status']??'completed');
        if (!in_array($status,['scheduled','completed','cancelled','no_show'],true)) throw new InvalidArgumentException('Invalid session status.');
        $serviceId=(int)($d['service_id']??0);
        $billable = ($status==='completed' && $serviceId>0) ? 1 : 0;
        if ($status==='completed' && $serviceId<1) {
            throw new InvalidArgumentException('Select a service for every completed session. Completed services are automatically billable.');
        }
        $appointmentId=(int)($d['appointment_id']??0);
        if ($appointmentId>0) {
            $appointment=$this->appointments->find($appointmentId);
            if ($appointment===null || (int)$appointment['patient_id']!==$patientId || (int)$appointment['therapist_id']!==$therapistId) {
                throw new InvalidArgumentException('The selected appointment does not match this patient and therapist.');
            }
        }
        return [
            'patient_id'=>$patientId,'therapist_id'=>$therapistId,'assignment_id'=>(int)$assignment['id'],'appointment_id'=>$appointmentId>0?$appointmentId:null,'session_date'=>$date,
            'start_time'=>$start!==''?$start:null,'end_time'=>$end!==''?$end:null,'duration_minutes'=>$duration,
            'service_id'=>$serviceId>0?$serviceId:null,
            'billable'=>$billable,
            'session_type'=>trim((string)($d['session_type']??''))?:null,'status'=>$status,
            'interventions'=>trim((string)($d['interventions']??''))?:null,'activities'=>trim((string)($d['activities']??''))?:null,
            'patient_response'=>trim((string)($d['patient_response']??''))?:null,'progress'=>trim((string)($d['progress']??''))?:null,
            'recommendations'=>trim((string)($d['recommendations']??''))?:null,'notes'=>trim((string)($d['notes']??''))?:null,
        ];
    }
}
