<?php

namespace Modules\ClinicCore\Entities;

use Illuminate\Database\Eloquent\Model;

class ClcVitalRange extends Model
{
    protected $table = 'clc_vital_ranges';
    protected $guarded = ['id'];

    /**
     * Map free-text species labels to the canonical keys used in the
     * seeded clc_vital_ranges rows. Pet records in this codebase use a
     * mix of "dog", "Dog", "canine", "cat", "Cat", etc. - the seed
     * uses "canine"/"feline" only. Without this normaliser, ranges
     * never resolve for owners who picked "Dog" from the species picker.
     */
    public static function normaliseSpecies(?string $species): ?string
    {
        if (!$species) return null;
        $s = strtolower(trim($species));
        $aliases = [
            'dog'        => 'canine',  'doggo'    => 'canine',  'puppy' => 'canine',
            'k9'         => 'canine',  'canine'   => 'canine',
            // Size-stratified canine keys (used when the species record
            // explicitly differentiates by size). Resolver falls back to
            // plain "canine" if the size-specific row doesn't exist.
            'toy_dog'    => 'canine_toy',   'small_dog' => 'canine_small',
            'medium_dog' => 'canine_med',   'large_dog' => 'canine_large',
            'giant_dog'  => 'canine_giant',
            'cat'        => 'feline',  'kitten'   => 'feline',  'kitty' => 'feline',
            'feline'     => 'feline',
            'rabbit'     => 'lagomorph','bunny'   => 'lagomorph','lagomorph' => 'lagomorph',
            'ferret'     => 'mustelid', 'mustelid' => 'mustelid',
            'bird'       => 'avian',   'parrot'  => 'avian',   'avian'    => 'avian',
            'budgie'     => 'avian',   'cockatiel' => 'avian', 'canary'   => 'avian',
            'horse'      => 'equine',  'equine'  => 'equine',  'pony'     => 'equine',
            'cow'        => 'bovine',  'cattle'  => 'bovine',  'bovine'   => 'bovine',
            'sheep'      => 'ovine',   'ovine'   => 'ovine',
            'goat'       => 'caprine', 'caprine' => 'caprine',
            'guinea pig' => 'rodent',  'guinea_pig' => 'rodent',
            'hamster'    => 'rodent',  'rat' => 'rodent', 'mouse' => 'rodent',
            'rodent'     => 'rodent',  'chinchilla' => 'rodent', 'gerbil' => 'rodent',
            'reptile'    => 'reptile', 'lizard'  => 'reptile', 'snake'    => 'reptile',
            'turtle'     => 'reptile', 'tortoise' => 'reptile', 'iguana' => 'reptile',
            'gecko'      => 'reptile', 'bearded_dragon' => 'reptile',
        ];
        return $aliases[$s] ?? $s;
    }

    /**
     * Resolve the best-matching range for a (business, species, age) tuple.
     * Tries: business override + life_stage -> business + all -> system + life_stage -> system + all -> null.
     *
     * Species is run through normaliseSpecies() first AND a fallback is
     * tried against the raw value, so "Dog" -> "canine" but a clinic
     * that seeded a custom "Dog" row still resolves.
     */
    public static function resolve(int $businessId, ?string $species, ?int $ageYears = null): ?self
    {
        if (!$species) return null;
        $rawSpecies        = strtolower(trim($species));
        $normalisedSpecies = self::normaliseSpecies($species);
        // Try the normalised key first, then the raw value as a fallback.
        // De-dup so we don't run the same query twice when they match.
        $speciesCandidates = array_values(array_unique([$normalisedSpecies, $rawSpecies]));

        $stage = $ageYears === null ? 'all'
              : ($ageYears < 1 ? 'pediatric' : ($ageYears >= 8 ? 'senior' : 'adult'));

        foreach ($speciesCandidates as $sp) {
            foreach ([
                ['business_id' => $businessId, 'life_stage' => $stage],
                ['business_id' => $businessId, 'life_stage' => 'all'],
                ['business_id' => null,        'life_stage' => $stage],
                ['business_id' => null,        'life_stage' => 'all'],
            ] as $crit) {
                $row = self::where('species', $sp)
                    ->where('life_stage', $crit['life_stage']);
                if ($crit['business_id'] === null) {
                    $row->whereNull('business_id');
                } else {
                    $row->where('business_id', $crit['business_id']);
                }
                $row = $row->first();
                if ($row) return $row;
            }
        }
        return null;
    }

    /**
     * Fixed adult-human reference bands for the "constants" the clinic captures.
     * Human patients have no species, so the species resolver never matches —
     * these bands drive both the live UI colouring and the critical-flagging
     * for human clinics. [low, high, unit]; 'low_only' metrics (SpO2) never
     * flag a high value.
     */
    public static function humanBands(): array
    {
        return [
            'temperature'      => ['low' => 36.1, 'high' => 37.5, 'unit' => '°C'],
            'heart_rate'       => ['low' => 60,   'high' => 100,  'unit' => 'bpm'],
            'respiratory_rate' => ['low' => 12,   'high' => 20,   'unit' => '/min'],
            'systolic_bp'      => ['low' => 90,   'high' => 130,  'unit' => 'mmHg'],
            'diastolic_bp'     => ['low' => 60,   'high' => 85,   'unit' => 'mmHg'],
            'spo2'             => ['low' => 95,   'high' => 100,  'unit' => '%', 'low_only' => true],
            'blood_glucose'    => ['low' => 70,   'high' => 140,  'unit' => 'mg/dL'],
            'bmi'              => ['low' => 18.5,  'high' => 24.9, 'unit' => 'kg/m²'],
        ];
    }

    /**
     * Classify a set of human vitals against humanBands().
     * normal | low | high | critical (>15% outside band), keyed by metric.
     */
    public static function annotateHuman(array $vitals): array
    {
        $out = [];
        foreach (self::humanBands() as $key => $band) {
            $v = $vitals[$key] ?? null;
            if ($v === null || $v === '') { continue; }
            $lo = (float) $band['low']; $hi = (float) $band['high']; $v = (float) $v;
            if ($v < $lo) {
                $out[$key] = ($v < $lo * 0.85) ? 'critical' : 'low';
            } elseif ($v > $hi) {
                if (!empty($band['low_only'])) { $out[$key] = 'normal'; }
                else { $out[$key] = ($v > $hi * 1.15) ? 'critical' : 'high'; }
            } else {
                $out[$key] = 'normal';
            }
        }
        return $out;
    }

    /**
     * Annotate a single vital reading with severity per metric:
     * normal | low | high | critical (where critical = > 15% outside band).
     */
    public function annotate(array $vital): array
    {
        $out = [];
        $checks = [
            ['temperature',      $this->temperature_low,      $this->temperature_high],
            ['heart_rate',       $this->heart_rate_low,       $this->heart_rate_high],
            ['respiratory_rate', $this->respiratory_rate_low, $this->respiratory_rate_high],
            ['weight_kg',        $this->weight_low_kg,        $this->weight_high_kg],
        ];
        foreach ($checks as [$key, $lo, $hi]) {
            $v = $vital[$key] ?? null;
            if ($v === null || $lo === null || $hi === null) { $out[$key] = 'unknown'; continue; }
            $lo = (float) $lo; $hi = (float) $hi; $v = (float) $v;
            if ($v < $lo) {
                $out[$key] = ($v < $lo * 0.85) ? 'critical' : 'low';
            } elseif ($v > $hi) {
                $out[$key] = ($v > $hi * 1.15) ? 'critical' : 'high';
            } else {
                $out[$key] = 'normal';
            }
        }
        return $out;
    }
}
