<?php

namespace Modules\OphthalmologyClinic\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Log;
use Modules\ClinicCore\Entities\ClcPatient;
use Modules\OphthalmologyClinic\Entities\OpthEyeExam;
use Modules\OphthalmologyClinic\Entities\OphthPatientProfile;

/**
 * Ophthalmology per-visit eye exam entry (OD/OS). On save it:
 *   1. persists the full historical exam row (trend-capable),
 *   2. syncs the latest values into opth_patient_profiles (the summary the
 *      existing screens keep reading),
 *   3. raises an idempotent critical_value medical alert when IOP > 30 in
 *      either eye (fail-soft; resolves it when the exam no longer breaches).
 *
 * Business-scoped, gated by opth.exams.manage.
 */
class OpthExamController extends Controller
{
    private function biz()
    {
        return session('user.business_id');
    }

    private function guardPatient($patientId): ClcPatient
    {
        if (! auth()->user() || ! auth()->user()->can('opth.exams.manage')) {
            abort(403, 'Unauthorized action.');
        }
        return ClcPatient::where('business_id', $this->biz())->findOrFail($patientId);
    }

    public function store(Request $request, $patientId)
    {
        $patient = $this->guardPatient($patientId);

        $data = $request->validate([
            'appointment_id'  => 'nullable|integer',
            'va_od'           => 'nullable|string|max:15',
            'va_os'           => 'nullable|string|max:15',
            'va_od_corrected' => 'nullable|string|max:15',
            'va_os_corrected' => 'nullable|string|max:15',
            'sphere_od'       => 'nullable|numeric', 'sphere_os'   => 'nullable|numeric',
            'cylinder_od'     => 'nullable|numeric', 'cylinder_os' => 'nullable|numeric',
            'axis_od'         => 'nullable|integer|min:0|max:180', 'axis_os' => 'nullable|integer|min:0|max:180',
            'add_od'          => 'nullable|numeric', 'add_os'      => 'nullable|numeric',
            'iop_od'          => 'nullable|numeric|min:0|max:99', 'iop_os' => 'nullable|numeric|min:0|max:99',
            'iop_method'      => 'nullable|string|in:' . implode(',', array_keys(OpthEyeExam::IOP_METHODS)),
            'pupils_note'     => 'nullable|string|max:255',
            'cup_disc_od'     => 'nullable|numeric|min:0|max:1', 'cup_disc_os' => 'nullable|numeric|min:0|max:1',
            'dr_grade_od'     => 'nullable|string|in:' . implode(',', array_keys(OpthEyeExam::DR_GRADES)),
            'dr_grade_os'     => 'nullable|string|in:' . implode(',', array_keys(OpthEyeExam::DR_GRADES)),
            'maculopathy_od'  => 'nullable|string|in:' . implode(',', array_keys(OpthEyeExam::MACULOPATHY)),
            'maculopathy_os'  => 'nullable|string|in:' . implode(',', array_keys(OpthEyeExam::MACULOPATHY)),
            // Slit-lamp / fundus free-text (stored as {"notes": "..."} JSON via the
            // model's array cast). Previously the columns existed but were never captured.
            'slit_lamp_notes' => 'nullable|string|max:2000',
            'fundus_notes'    => 'nullable|string|max:2000',
            // Target IOP (persisted onto the 1:1 profile, not the exam row).
            'target_iop_od'   => 'nullable|numeric|min:0|max:99',
            'target_iop_os'   => 'nullable|numeric|min:0|max:99',
            'notes'           => 'nullable|string|max:2000',
        ]);

        $blank = fn ($k) => (array_key_exists($k, $data) && $data[$k] !== '') ? $data[$k] : null;

        $exam = OpthEyeExam::create([
            'business_id'     => $this->biz(),
            'patient_id'      => $patient->id,
            'appointment_id'  => $blank('appointment_id'),
            'va_od'           => $blank('va_od'),           'va_os'           => $blank('va_os'),
            'va_od_corrected' => $blank('va_od_corrected'), 'va_os_corrected' => $blank('va_os_corrected'),
            'sphere_od'       => $blank('sphere_od'),       'sphere_os'       => $blank('sphere_os'),
            'cylinder_od'     => $blank('cylinder_od'),     'cylinder_os'     => $blank('cylinder_os'),
            'axis_od'         => $blank('axis_od'),         'axis_os'         => $blank('axis_os'),
            'add_od'          => $blank('add_od'),          'add_os'          => $blank('add_os'),
            'iop_od'          => $blank('iop_od'),          'iop_os'          => $blank('iop_os'),
            'iop_method'      => $blank('iop_method'),
            'pupils_note'     => $blank('pupils_note'),
            'cup_disc_od'     => $blank('cup_disc_od'),     'cup_disc_os'     => $blank('cup_disc_os'),
            'dr_grade_od'     => $blank('dr_grade_od'),     'dr_grade_os'     => $blank('dr_grade_os'),
            'maculopathy_od'  => $blank('maculopathy_od'),  'maculopathy_os'  => $blank('maculopathy_os'),
            // {"notes": "..."} JSON (model casts slit_lamp/fundus to array), or null.
            'slit_lamp'       => $blank('slit_lamp_notes') !== null ? ['notes' => $data['slit_lamp_notes']] : null,
            'fundus'          => $blank('fundus_notes') !== null ? ['notes' => $data['fundus_notes']] : null,
            'notes'           => $blank('notes'),
            'examined_by'     => auth()->id(),
            'examined_at'     => now(),
        ]);

        $this->syncProfile($patient, $exam, $data);
        $this->syncIopAlert($patient, $exam);

        return response()->json(['success' => true, 'exam_id' => $exam->id, 'msg' => 'Eye exam saved.']);
    }

    /**
     * Mirror the latest exam values into the 1:1 summary profile so the existing
     * at-a-glance screens keep working. Maps OD/OS -> the profile's right/left
     * columns. Only overwrites a summary field when this exam provided a value.
     */
    private function syncProfile(ClcPatient $patient, OpthEyeExam $exam, array $data = []): void
    {
        $map = [
            'va_right'      => $exam->va_od,
            'va_left'       => $exam->va_os,
            'sphere_right'  => $exam->sphere_od,
            'sphere_left'   => $exam->sphere_os,
            'cylinder_right'=> $exam->cylinder_od,
            'cylinder_left' => $exam->cylinder_os,
            'axis_right'    => $exam->axis_od,
            'axis_left'     => $exam->axis_os,
            'iop_right'     => $exam->iop_od,
            'iop_left'      => $exam->iop_os,
            // Target IOP is a clinician-set goal (not a measured exam value); it
            // comes straight from the request and lives only on the profile.
            'target_iop_od' => (array_key_exists('target_iop_od', $data) && $data['target_iop_od'] !== '' && $data['target_iop_od'] !== null) ? $data['target_iop_od'] : null,
            'target_iop_os' => (array_key_exists('target_iop_os', $data) && $data['target_iop_os'] !== '' && $data['target_iop_os'] !== null) ? $data['target_iop_os'] : null,
        ];
        $update = array_filter($map, fn ($v) => $v !== null);
        if (empty($update)) return;

        OphthPatientProfile::updateOrCreate(['clc_patient_id' => $patient->id], $update);
    }

    /**
     * IOP > 30 in either eye -> critical_value medical alert. Idempotent per
     * exam via (source_type, source_id); resolves when the exam no longer
     * breaches. Fully fail-soft — never blocks the clinical save.
     */
    private function syncIopAlert(ClcPatient $patient, OpthEyeExam $exam): void
    {
        try {
            if (! class_exists(\Modules\ClinicCore\Entities\Services\MedicalAlertService::class)) {
                return;
            }
            $svc = app(\Modules\ClinicCore\Entities\Services\MedicalAlertService::class);
            $bizId = (int) $patient->business_id;

            if ($exam->iopCritical()) {
                $parts = [];
                if ($exam->iop_od !== null) $parts[] = 'OD ' . rtrim(rtrim(number_format($exam->iop_od, 1), '0'), '.') . ' mmHg';
                if ($exam->iop_os !== null) $parts[] = 'OS ' . rtrim(rtrim(number_format($exam->iop_os, 1), '0'), '.') . ' mmHg';
                $svc->raise([
                    'business_id' => $bizId,
                    'patient_id'  => (int) $patient->id,
                    'alert_type'  => 'critical_value',
                    'severity'    => 'critical',
                    'title'       => 'Critical IOP (> ' . (int) OpthEyeExam::IOP_CRITICAL . ' mmHg)',
                    'details'     => implode(', ', $parts),
                    'source_type' => 'opth_eye_exams',
                    'source_id'   => (int) $exam->id,
                ]);
            } else {
                $svc->resolveBySource($bizId, (int) $patient->id, 'opth_eye_exams', (int) $exam->id);
            }
        } catch (\Throwable $e) {
            Log::warning('OphthalmologyClinic IOP alert sync failed: ' . $e->getMessage());
        }
    }
}
