<?php

namespace Modules\ClinicCore\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Schema;
use Modules\ClinicCore\Entities\ClcAppointment;
use Modules\ClinicCore\Entities\ClcPatient;
use Modules\ClinicCore\Entities\ClcVital;
use Modules\ClinicCore\Entities\ClcVitalRange;
use Modules\ClinicCore\Entities\Services\MedicalAlertService;

/**
 * SIFA P3-T4 — trend-capable vitals.
 *
 * The appointment vitals form saves a clc_vitals row AND keeps the legacy
 * clc_appointments vitals columns in sync (dual-write), so every old screen /
 * report keeps working. Out-of-range values are flagged via the existing
 * clc_vital_ranges engine and a critical reading raises a Wave-A medical alert
 * (idempotent per appointment + measure).
 */
class ClcVitalsController extends Controller
{
    /** Human vital measures the range engine can flag + their clc_vitals cols. */
    private const RANGE_MEASURES = [
        'temperature'      => 'temperature',
        'heart_rate'       => 'heart_rate',
        'respiratory_rate' => 'respiratory_rate',
        'systolic_bp'      => 'bp_systolic',
        'diastolic_bp'     => 'bp_diastolic',
        'spo2'             => 'spo2',
        'blood_glucose'    => 'blood_glucose',
        'bmi'              => 'bmi',
    ];

    private function businessId(): int
    {
        return (int) request()->session()->get('user.business_id');
    }

    /**
     * Store a vitals reading against an appointment (dual-write).
     * ABAC vitals-group gate is consulted when MedOrganization is installed.
     */
    public function store(Request $request, $appointmentId)
    {
        if (!auth()->user()->can('clinic.vitals.record') && !auth()->user()->can('clinic.manage_appointments')) {
            abort(403, 'Unauthorized action.');
        }

        // ABAC: nurse-vitals-only / field-group gate (fail-open when absent).
        if (!$this->vitalsFieldEditable()) {
            return response()->json([
                'success' => false,
                'msg'     => __('clinicCore::lang.vitals_not_permitted'),
            ], 403);
        }

        $businessId = $this->businessId();
        $appt = ClcAppointment::where('business_id', $businessId)->findOrFail($appointmentId);

        $num = fn ($key) => ($request->input($key) === '' || $request->input($key) === null)
            ? null : $request->input($key);

        $glucoseTiming = $request->input('glucose_timing');

        $vital = new ClcVital([
            'business_id'      => $businessId,
            'patient_id'       => $appt->patient_id,
            'appointment_id'   => $appt->id,
            'recorded_by'      => auth()->id(),
            'recorded_at'      => now(),
            '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'),
            'weight_kg'        => $num('weight_kg'),
            'height_cm'        => $num('height_cm'),
            'pain_score'       => $num('pain_score'),
            'blood_glucose'    => $num('blood_glucose'),
            'glucose_timing'   => in_array($glucoseTiming, ClcVital::GLUCOSE_TIMINGS, true) ? $glucoseTiming : null,
            'gcs_eye'          => $num('gcs_eye'),
            'gcs_verbal'       => $num('gcs_verbal'),
            'gcs_motor'        => $num('gcs_motor'),
            'notes'            => $request->input('notes') ?: null,
        ]);
        $vital->save();   // bmi + gcs_total auto-derived in the model

        // ── Dual-write: keep the legacy clc_appointments snapshot in sync ──
        $legacy = array_filter([
            'weight_kg'        => $vital->weight_kg,
            'temperature'      => $vital->temperature,
            'heart_rate'       => $vital->heart_rate,
            'respiratory_rate' => $vital->respiratory_rate,
            'systolic_bp'      => $vital->bp_systolic,
            'diastolic_bp'     => $vital->bp_diastolic,
            'spo2'             => $vital->spo2,
            'blood_glucose'    => $vital->blood_glucose,
            'bmi'              => $vital->bmi,
        ], fn ($v) => $v !== null);
        foreach (array_keys($legacy) as $col) {
            if (!Schema::hasColumn('clc_appointments', $col)) { unset($legacy[$col]); }
        }
        if ($legacy) {
            $appt->update($legacy);
        }

        // ── Flagging + critical alert ──
        $annotation = $this->flag($appt, $vital);
        $this->raiseCriticalAlerts($appt, $annotation);

        return response()->json([
            'success'    => true,
            'msg'        => __('clinicCore::lang.saved_successfully'),
            'vital'      => $vital,
            'annotation' => $annotation,
        ]);
    }

    /**
     * Vitals trend view for a patient: last N readings + a chart (Chart.js when
     * available, table-only fallback otherwise).
     */
    public function trends(Request $request, $patientId)
    {
        if (!auth()->user()->can('clinic.vitals.view_trends') && !auth()->user()->can('clinic.view_appointments')) {
            abort(403, 'Unauthorized action.');
        }
        $businessId = $this->businessId();
        $patient = ClcPatient::where('business_id', $businessId)->findOrFail($patientId);

        $limit    = (int) $request->input('limit', 30);
        $readings = ClcVital::where('business_id', $businessId)
            ->where('patient_id', $patient->id)
            ->orderByDesc('recorded_at')
            ->limit($limit > 0 && $limit <= 200 ? $limit : 30)
            ->get();

        return view('clinicCore::vitals.trends', compact('patient', 'readings'));
    }

    // ─────────────────────────────────────────────────────────────────────
    // P3-T3 · Voice dictation hook (AiCore transcriber)
    // ─────────────────────────────────────────────────────────────────────

    /**
     * Transcribe a recorded SOAP dictation via the shared AiCore engine. Ships
     * a graceful "transcription unavailable" JSON when AiCore (or a server-side
     * transcription provider) is absent, so the mic button never hard-fails.
     */
    public function dictate(Request $request, $appointmentId)
    {
        if (!auth()->user()->can('clinic.manage_appointments')) {
            abort(403, 'Unauthorized action.');
        }

        $businessId = $this->businessId();
        // Scope-check the appointment so a dictation can't be POSTed cross-tenant.
        ClcAppointment::where('business_id', $businessId)->findOrFail($appointmentId);

        try {
            $mu = app(\App\Utils\ModuleUtil::class);
            if (!$mu->isModuleInstalled('AiCore')) {
                return $this->dictationUnavailable();
            }

            $providerClass = '\Modules\AiCore\Entities\AiProvider';
            $transcriber   = '\Modules\AiCore\Entities\Services\AiTranscriber';
            if (!class_exists($providerClass) || !class_exists($transcriber)) {
                return $this->dictationUnavailable();
            }

            if (!$request->hasFile('audio')) {
                return response()->json(['success' => false, 'msg' => __('clinicCore::lang.no_audio')], 422);
            }

            /** @var \Modules\AiCore\Entities\AiProvider $settings */
            $settings = $providerClass::forBusiness($businessId);

            // A server-side transcription driver must be configured (webspeech
            // runs in the browser and never reaches this endpoint).
            $cfg = method_exists($settings, 'transcriptionConfig') ? $settings->transcriptionConfig() : [];
            if (empty($cfg) || ($cfg['provider'] ?? 'webspeech') === 'webspeech') {
                return $this->dictationUnavailable();
            }

            $prompt = 'Clinical SOAP note: symptoms, examination findings, diagnosis, medications, dosage, follow-up.';
            $result = $transcriber::transcribe($settings, $request->file('audio'), $cfg['language'] ?? null, $prompt);

            return response()->json([
                'success' => true,
                'text'    => $result['text'] ?? '',
            ]);
        } catch (\Throwable $e) {
            \Log::warning('ClinicCore SOAP dictation failed: ' . $e->getMessage());
            return $this->dictationUnavailable();
        }
    }

    private function dictationUnavailable()
    {
        return response()->json([
            'success'   => false,
            'available' => false,
            'msg'       => __('clinicCore::lang.transcription_unavailable'),
        ]);
    }

    // ─────────────────────────────────────────────────────────────────────
    // Flagging helpers
    // ─────────────────────────────────────────────────────────────────────

    /**
     * Annotate a reading against the reference ranges. Uses the species/age
     * range when one resolves (vet clinics), otherwise the fixed human bands —
     * mirrors ClcAppointmentController::recomputeVitalCriticalFlags().
     *
     * @return array<string,string> spec measure => normal|low|high|critical
     */
    private function flag(ClcAppointment $appt, ClcVital $vital): array
    {
        $patient  = $appt->pet;
        $species  = $patient ? strtolower($patient->species ?? '') : null;
        $ageYears = ($patient && $patient->dob)
            ? (int) \Carbon\Carbon::parse($patient->dob)->diffInYears(now())
            : null;

        $range = ClcVitalRange::resolve((int) $appt->business_id, $species, $ageYears);
        if ($range) {
            $annotation = $range->annotate([
                'temperature'      => $vital->temperature,
                'heart_rate'       => $vital->heart_rate,
                'respiratory_rate' => $vital->respiratory_rate,
                'weight_kg'        => $vital->weight_kg,
            ]);
        } else {
            $annotation = ClcVitalRange::annotateHuman([
                'temperature'      => $vital->temperature,
                'heart_rate'       => $vital->heart_rate,
                'respiratory_rate' => $vital->respiratory_rate,
                'systolic_bp'      => $vital->bp_systolic,
                'diastolic_bp'     => $vital->bp_diastolic,
                'spo2'             => $vital->spo2,
                'blood_glucose'    => $vital->blood_glucose,
                'bmi'              => $vital->bmi,
            ]);
        }

        return $annotation;
    }

    /**
     * Raise a Wave-A critical_value alert per critical measure. Idempotent per
     * appointment + measure via source_type/source_id (MedicalAlertService
     * upserts on those). Fail-soft.
     */
    private function raiseCriticalAlerts(ClcAppointment $appt, array $annotation): void
    {
        $critical = array_keys(array_filter($annotation, fn ($sev) => $sev === 'critical'));
        if (empty($critical)) {
            return;
        }

        try {
            $svc = app(MedicalAlertService::class);
            foreach ($critical as $measure) {
                $label = __('clinicCore::lang.' . $measure);
                if (str_starts_with($label, 'clinicCore::')) {
                    $label = ucwords(str_replace('_', ' ', $measure));
                }
                $svc->raise([
                    'business_id' => (int) $appt->business_id,
                    'patient_id'  => (int) $appt->patient_id,
                    'alert_type'  => 'critical_value',
                    'severity'    => 'critical',
                    'title'       => __('clinicCore::lang.critical_vital') . ': ' . $label,
                    'details'     => __('clinicCore::lang.critical_vital_details', [
                        'measure' => $label,
                        'ref'     => '#' . $appt->id,
                    ]),
                    // Idempotency key: one alert per appointment + measure.
                    'source_type' => 'clc_appointment_vital:' . $measure,
                    'source_id'   => (int) $appt->id,
                ]);
            }
        } catch (\Throwable $e) {
            \Log::warning('ClinicCore critical-vital alert raise failed: ' . $e->getMessage());
        }
    }

    /**
     * ABAC vitals field-group gate. Consult MedOrganization's MedAccess when
     * that module is installed; fail-open (true) when the module/class is
     * absent so vitals capture keeps working on stock installs.
     */
    private function vitalsFieldEditable(): bool
    {
        try {
            $mu = app(\App\Utils\ModuleUtil::class);
            if (!$mu->isModuleInstalled('MedOrganization')) {
                return true;
            }
            $accessClass = '\Modules\MedOrganization\Services\MedAccess';
            if (!class_exists($accessClass)) {
                return true;
            }
            return (bool) app($accessClass)->fieldGroupEditable(auth()->user(), 'vitals');
        } catch (\Throwable $e) {
            return true; // fail-open
        }
    }
}
