<?php

namespace Modules\ClinicCore\Entities\Services;

use Modules\ClinicCore\Entities\ClcMedicine;
use Modules\ClinicCore\Entities\ClcPatient;
use Modules\ClinicCore\Entities\ClcPatientAllergy;
use Modules\ClinicCore\Entities\ClcPrescription;
use Modules\ClinicCore\Entities\ClcPrescriptionItem;

/**
 * SIFA P3-T5 — deterministic (non-AI) prescription safety screen and the mount
 * point for the future drug-interaction engine (Phase 4).
 *
 * check($patientId, $medicineIds) returns a warnings[] list. Each warning is:
 *   ['type' => allergy|duplicate_therapy|interaction,
 *    'severity' => info|warning|severe|life_threatening,
 *    'message' => '<human sentence>',
 *    'medicine_id' => ?int, 'block' => bool]
 *
 * Rules implemented now:
 *   (a) drug-allergy — active type=drug allergies whose allergen text matches a
 *       medicine name/generic (case-insensitive substring, both directions).
 *       severe / life_threatening hits BLOCK (block=true) and also raise a
 *       drug_interaction medical alert (idempotent per medicine).
 *   (b) duplicate-therapy — the same medicine already on an ACTIVE prescription
 *       for the patient.
 *   (c) extension point — if \Modules\MedInteractions\Services\InteractionEngine
 *       exists (future module), its warnings are merged in (fail-soft).
 *
 * Business-scoped throughout. No AI, no external calls — safe to run inline on
 * the create/store flow.
 */
class RxSafetyService
{
    public function __construct(
        protected ?MedicalAlertService $alerts = null
    ) {
        $this->alerts = $alerts ?: app(MedicalAlertService::class);
    }

    /**
     * @param int   $patientId
     * @param int[] $medicineIds  clc_medicines ids proposed for this Rx
     * @param array $opts         optional: business_id (defaults to session),
     *                            raise_alerts (bool, default true)
     * @return array<int, array<string, mixed>> warnings
     */
    public function check(int $patientId, array $medicineIds, array $opts = []): array
    {
        $businessId = (int) ($opts['business_id'] ?? session('user.business_id'));
        $raise      = $opts['raise_alerts'] ?? true;

        $medicineIds = array_values(array_unique(array_filter(array_map('intval', $medicineIds))));
        if (empty($medicineIds) || !$patientId || !$businessId) {
            return [];
        }

        $patient = ClcPatient::where('business_id', $businessId)->find($patientId);
        if (!$patient) {
            return [];
        }

        $medicines = ClcMedicine::where('business_id', $businessId)
            ->whereIn('id', $medicineIds)->get();
        if ($medicines->isEmpty()) {
            return [];
        }

        $warnings = [];

        $warnings = array_merge($warnings, $this->allergyWarnings($businessId, $patient, $medicines, $raise));
        $warnings = array_merge($warnings, $this->duplicateTherapyWarnings($businessId, $patientId, $medicines));
        $warnings = array_merge($warnings, $this->engineWarnings($businessId, $patientId, $medicines->pluck('id')->all()));

        return $warnings;
    }

    /** (a) drug-allergy cross-check against structured allergy rows. */
    protected function allergyWarnings(int $businessId, ClcPatient $patient, $medicines, bool $raise): array
    {
        $allergies = ClcPatientAllergy::where('business_id', $businessId)
            ->where('patient_id', $patient->id)
            ->where('type', 'drug')
            ->where('status', 'active')
            ->get();

        if ($allergies->isEmpty()) {
            return [];
        }

        $warnings = [];
        foreach ($medicines as $med) {
            $names = array_filter([
                mb_strtolower(trim((string) $med->name)),
                mb_strtolower(trim((string) $med->generic_name)),
            ]);
            foreach ($allergies as $allergy) {
                $allergen = mb_strtolower(trim((string) $allergy->allergen));
                if ($allergen === '') {
                    continue;
                }
                $hit = false;
                foreach ($names as $name) {
                    if ($name === '') {
                        continue;
                    }
                    // Case-insensitive substring both directions.
                    if (mb_strpos($name, $allergen) !== false || mb_strpos($allergen, $name) !== false) {
                        $hit = true;
                        break;
                    }
                }
                if (!$hit) {
                    continue;
                }

                $severe = in_array($allergy->severity, ['severe', 'life_threatening'], true);
                $warnings[] = [
                    'type'        => 'allergy',
                    'severity'    => $allergy->severity,
                    'message'     => trim(__('clinicCore::lang.rx_allergy_warning', [
                        'medicine' => $med->name,
                        'allergen' => $allergy->allergen,
                        'severity' => $allergy->severity,
                    ])),
                    'medicine_id' => (int) $med->id,
                    'block'       => $severe,
                ];

                // Severe / life-threatening allergy hits raise a drug_interaction
                // medical alert (idempotent per patient + medicine).
                if ($severe && $raise) {
                    try {
                        $this->alerts->raise([
                            'business_id' => $businessId,
                            'patient_id'  => (int) $patient->id,
                            'alert_type'  => 'drug_interaction',
                            'severity'    => $allergy->severity === 'life_threatening' ? 'critical' : 'warning',
                            'title'       => trim(__('clinicCore::lang.rx_allergy_alert_title', ['medicine' => $med->name])),
                            'details'     => trim(__('clinicCore::lang.rx_allergy_warning', [
                                'medicine' => $med->name,
                                'allergen' => $allergy->allergen,
                                'severity' => $allergy->severity,
                            ])),
                            'source_type' => 'clc_rx_allergy_check',
                            'source_id'   => (int) $med->id,
                        ]);
                    } catch (\Throwable $e) {
                        \Log::warning('RxSafetyService alert raise skipped: ' . $e->getMessage());
                    }
                }
            }
        }

        return $warnings;
    }

    /** (b) duplicate-therapy — medicine already on an active Rx for the patient. */
    protected function duplicateTherapyWarnings(int $businessId, int $patientId, $medicines): array
    {
        $activeMedicineIds = ClcPrescriptionItem::query()
            ->join('clc_prescriptions as p', 'p.id', '=', 'clc_prescription_items.prescription_id')
            ->where('p.business_id', $businessId)
            ->where('p.patient_id', $patientId)
            ->where('p.status', 'active')
            ->whereNotNull('clc_prescription_items.medicine_id')
            ->pluck('clc_prescription_items.medicine_id')
            ->map(fn ($v) => (int) $v)
            ->unique()
            ->all();

        if (empty($activeMedicineIds)) {
            return [];
        }

        $warnings = [];
        foreach ($medicines as $med) {
            if (in_array((int) $med->id, $activeMedicineIds, true)) {
                $warnings[] = [
                    'type'        => 'duplicate_therapy',
                    'severity'    => 'warning',
                    'message'     => trim(__('clinicCore::lang.rx_duplicate_warning', ['medicine' => $med->name])),
                    'medicine_id' => (int) $med->id,
                    'block'       => false,
                ];
            }
        }

        return $warnings;
    }

    /**
     * (c) Extension point — delegate to a future drug-interaction engine module
     * when present. Fail-soft: any absence / error is swallowed.
     *
     * @param int[] $medicineIds
     */
    protected function engineWarnings(int $businessId, int $patientId, array $medicineIds): array
    {
        $class = '\Modules\MedInteractions\Services\InteractionEngine';
        if (!class_exists($class)) {
            return [];
        }
        try {
            $engine = app($class);
            if (!method_exists($engine, 'check')) {
                return [];
            }
            $extra = $engine->check($patientId, $medicineIds, ['business_id' => $businessId]);
            if (!is_array($extra)) {
                return [];
            }
            // Normalise to our warning shape (best-effort).
            return array_values(array_map(function ($w) {
                $w = (array) $w;
                return [
                    'type'        => (string) ($w['type'] ?? 'interaction'),
                    'severity'    => (string) ($w['severity'] ?? 'warning'),
                    'message'     => (string) ($w['message'] ?? ''),
                    'medicine_id' => isset($w['medicine_id']) ? (int) $w['medicine_id'] : null,
                    'block'       => (bool) ($w['block'] ?? false),
                ];
            }, $extra));
        } catch (\Throwable $e) {
            \Log::warning('RxSafetyService InteractionEngine delegation skipped: ' . $e->getMessage());
            return [];
        }
    }

    /** Whether any warning in the list blocks the save (needs an explicit confirm). */
    public static function hasBlocking(array $warnings): bool
    {
        foreach ($warnings as $w) {
            if (!empty($w['block'])) {
                return true;
            }
        }
        return false;
    }
}
