<?php

namespace Modules\MedEmergency\Services;

use Illuminate\Support\Facades\Schema;
use Modules\ClinicCore\Entities\ClcVital;
use Modules\ClinicCore\Entities\Services\MedicalAlertService;
use Modules\MedEmergency\Entities\MderVisit;
use Modules\MedEmergency\Entities\MderVisitEvent;

/**
 * Triage engine for the ER (SIFA P6-T3).
 *
 * Reuse-first: triage vitals are written as a real ClinicCore clc_vitals row
 * (appointment_id null — an ad-hoc ER reading), and a resuscitation / emergent
 * triage raises a ClinicCore critical MedicalAlert. Both integrations are
 * fail-soft so the module degrades gracefully when ClinicCore is partly present.
 */
class TriageService
{
    /**
     * Auto-suggest a triage level from raw vitals, per the configurable
     * thresholds. Returns the SUGGESTED level (the triager may override) plus a
     * human list of the reasons that fired.
     *
     *   GCS < 9                                   → resuscitation
     *   RR > 30  OR  SpO2 < 90  OR  HR > 130       → emergent
     *   (nothing fired)                            → null (no auto-suggestion)
     *
     * @param array $vitals heart_rate, respiratory_rate, spo2, gcs_total (or the
     *                      three gcs components), … — nulls tolerated.
     * @return array{level: ?string, reasons: string[]}
     */
    public function suggestLevel(array $vitals): array
    {
        $cfg = config('medemergency.triage_suggest', [
            'rr_high' => 30, 'spo2_low' => 90, 'hr_high' => 130, 'gcs_low' => 9,
        ]);

        $num = fn ($k) => (isset($vitals[$k]) && $vitals[$k] !== '' && $vitals[$k] !== null) ? (float) $vitals[$k] : null;

        $rr   = $num('respiratory_rate');
        $spo2 = $num('spo2');
        $hr   = $num('heart_rate');

        $gcs = $num('gcs_total');
        if ($gcs === null) {
            $e = $num('gcs_eye');
            $v = $num('gcs_verbal');
            $m = $num('gcs_motor');
            if ($e !== null && $v !== null && $m !== null) {
                $gcs = $e + $v + $m;
            }
        }

        $reasons = [];
        $level = null;

        // Resuscitation wins outright (GCS is the highest-acuity signal).
        if ($gcs !== null && $gcs < $cfg['gcs_low']) {
            $level = 'resuscitation';
            $reasons[] = 'GCS ' . (int) $gcs . ' < ' . $cfg['gcs_low'];
        }

        // Emergent+ escalators.
        if ($rr !== null && $rr > $cfg['rr_high']) {
            $reasons[] = 'RR ' . (int) $rr . ' > ' . $cfg['rr_high'];
            $level = $level ?: 'emergent';
        }
        if ($spo2 !== null && $spo2 < $cfg['spo2_low']) {
            $reasons[] = 'SpO2 ' . (int) $spo2 . '% < ' . $cfg['spo2_low'] . '%';
            $level = $level ?: 'emergent';
        }
        if ($hr !== null && $hr > $cfg['hr_high']) {
            $reasons[] = 'HR ' . (int) $hr . ' > ' . $cfg['hr_high'];
            $level = $level ?: 'emergent';
        }

        return ['level' => $level, 'reasons' => $reasons];
    }

    /**
     * Persist a triage vitals reading as a ClinicCore clc_vitals row (ad-hoc ER
     * reading — appointment_id null). Returns the new row id, or null when
     * ClinicCore vitals are unavailable / the reading is empty. Fail-soft.
     */
    public function writeTriageVitals(MderVisit $visit, array $vitals): ?int
    {
        if (! Schema::hasTable('clc_vitals') || ! class_exists(ClcVital::class)) {
            return null;
        }
        // A patient row is required (clc_vitals.patient_id is NOT NULL + FK).
        if (empty($visit->patient_id)) {
            return null;
        }

        $num = fn ($k) => (isset($vitals[$k]) && $vitals[$k] !== '' && $vitals[$k] !== null) ? $vitals[$k] : null;

        $payload = [
            'temperature'      => $num('temperature'),
            'heart_rate'       => $num('heart_rate'),
            'respiratory_rate' => $num('respiratory_rate'),
            'bp_systolic'      => $num('bp_systolic'),
            'bp_diastolic'     => $num('bp_diastolic'),
            'spo2'             => $num('spo2'),
            'pain_score'       => $num('pain_score'),
            'gcs_eye'          => $num('gcs_eye'),
            'gcs_verbal'       => $num('gcs_verbal'),
            'gcs_motor'        => $num('gcs_motor'),
        ];

        // Nothing meaningful to store → skip.
        if (! collect($payload)->filter(fn ($v) => $v !== null)->count()) {
            return null;
        }

        try {
            $vital = new ClcVital(array_merge($payload, [
                'business_id'    => $visit->business_id,
                'patient_id'     => $visit->patient_id,
                'appointment_id' => null,
                'recorded_by'    => auth()->id(),
                'recorded_at'    => now(),
                'notes'          => 'ER triage — ' . $visit->visit_no,
            ]));
            $vital->save(); // bmi + gcs_total auto-derived in the model

            return (int) $vital->id;
        } catch (\Throwable $e) {
            \Log::warning('MedEmergency triage vitals write skipped: ' . $e->getMessage());

            return null;
        }
    }

    /**
     * Raise a ClinicCore critical medical alert for a high-acuity triage
     * (resuscitation / emergent). Idempotent per visit (keyed on the visit as the
     * alert source). Fail-soft: absence of ClinicCore alerts is swallowed.
     */
    public function raiseCriticalAlert(MderVisit $visit, string $level): void
    {
        if (! in_array($level, ['resuscitation', 'emergent'], true)) {
            return;
        }
        if (empty($visit->patient_id) || ! class_exists(MedicalAlertService::class)) {
            return;
        }

        try {
            app(MedicalAlertService::class)->raise([
                'business_id' => (int) $visit->business_id,
                'patient_id'  => (int) $visit->patient_id,
                'alert_type'  => 'other',
                'severity'    => 'critical',
                'title'       => 'ER triage: ' . ucfirst(str_replace('_', ' ', $level)),
                'details'     => trim(($visit->chief_complaint ?: '') . ' (' . $visit->visit_no . ')'),
                'source_type' => 'mder_visits',
                'source_id'   => (int) $visit->id,
            ]);
        } catch (\Throwable $e) {
            \Log::warning('MedEmergency critical triage alert skipped: ' . $e->getMessage());
        }
    }

    /**
     * Apply a triage decision to a visit: write vitals, stamp the triage fields,
     * advance the board and raise a critical alert for high-acuity levels. All in
     * one place so the controller stays thin.
     */
    public function applyTriage(MderVisit $visit, string $level, string $system, array $vitals, ?int $assignedDoctorId = null): MderVisit
    {
        $vitalsId = $this->writeTriageVitals($visit, $vitals);

        $visit->triage_level      = $level;
        $visit->triage_system     = in_array($system, MderVisit::TRIAGE_SYSTEMS, true) ? $system : 'esi';
        $visit->triaged_by        = auth()->id();
        $visit->triaged_at        = now();
        $visit->triage_vitals_id  = $vitalsId;
        if ($assignedDoctorId) {
            $visit->assigned_doctor_id = $assignedDoctorId;
        }
        // Awaiting-triage visits advance to "to be seen" once triaged.
        if ($visit->board_status === 'awaiting_triage') {
            $visit->board_status = 'to_be_seen';
        }
        $visit->save();

        MderVisitEvent::log(
            $visit->id,
            'triaged',
            'Triaged ' . $level . ' (' . strtoupper($visit->triage_system) . ')'
        );

        $this->raiseCriticalAlert($visit, $level);

        return $visit;
    }
}
