"""Minto 3-compartment pharmacokinetic model for remifentanil.

The Minto model describes remifentanil distribution using a three-compartment
model with covariate-adjusted parameters (age, lean body mass) and an
effect-site compartment.

References:
    Minto, C. F., Schnider, T. W., Egan, T. D., et al. (1997).
    Influence of age and gender on the pharmacokinetics and pharmacodynamics
    of remifentanil. Anesthesiology, 86(1), 10-23.

    Minto, C. F., Schnider, T. W., & Shafer, S. L. (1997).
    Pharmacokinetics and pharmacodynamics of remifentanil. II. Model
    application. Anesthesiology, 86(1), 24-33.
"""

from __future__ import annotations

import numpy as np


# SIMPLIFICATION: Using default covariate values (age=40, lbm=55 kg)
# instead of patient-specific covariate adjustment. Clinical Minto model
# adjusts V1, V2, Cl1, Cl2, Cl3 based on age, gender, and lean body mass.
# Default values yield standard adult PK for benchmark environments.

PARAMETER_RANGES = {
    "V1": {
        "min": 3.0, "max": 8.0, "unit": "L",
        "source": "Minto et al., 1997", "default": 5.1,
    },
    "V2": {
        "min": 6.0, "max": 14.0, "unit": "L",
        "source": "Minto et al., 1997", "default": 9.82,
    },
    "V3": {
        "min": 3.0, "max": 8.0, "unit": "L",
        "source": "Minto et al., 1997", "default": 5.42,
    },
    "Cl1": {
        "min": 1.5, "max": 4.0, "unit": "L/min",
        "source": "Minto et al., 1997", "default": 2.6,
    },
    "Cl2": {
        "min": 1.0, "max": 3.5, "unit": "L/min",
        "source": "Minto et al., 1997", "default": 2.05,
    },
    "Cl3": {
        "min": 0.03, "max": 0.15, "unit": "L/min",
        "source": "Minto et al., 1997", "default": 0.076,
    },
    "ke0": {
        "min": 0.3, "max": 0.9, "unit": "1/min",
        "source": "Minto et al., 1997", "default": 0.595,
    },
}


class MintoPKModel:
    """Three-compartment PK model for remifentanil (Minto parameterization).

    State variables:
        A1: amount in central compartment (mcg)
        A2: amount in rapid peripheral compartment (mcg)
        A3: amount in slow peripheral compartment (mcg)
        Ce: effect-site concentration (ng/mL)

    Plasma concentration: Cp = A1 / V1 (ng/mL when A1 in ng, or mcg/L when A1 in mcg).
    We track in mcg and convert to ng/mL: Cp_ng = (A1_mcg / V1_L) = mcg/L = ng/mL.
    """

    def __init__(
        self,
        V1: float = 5.1,
        V2: float = 9.82,
        V3: float = 5.42,
        Cl1: float = 2.6,
        Cl2: float = 2.05,
        Cl3: float = 0.076,
        ke0: float = 0.595,
        dt: float = 0.5,
        weight_kg: float = 70.0,
    ) -> None:
        self.V1 = V1
        self.V2 = V2
        self.V3 = V3
        self.Cl1 = Cl1
        self.Cl2 = Cl2
        self.Cl3 = Cl3
        self.ke0 = ke0
        self.dt = dt
        self.weight_kg = weight_kg

        # Derive micro-constants from clearances and volumes
        self.k10 = Cl1 / V1
        self.k12 = Cl2 / V1
        self.k21 = Cl2 / V2
        self.k13 = Cl3 / V1
        self.k31 = Cl3 / V3

        self.A1 = 0.0  # mcg in central
        self.A2 = 0.0  # mcg in rapid peripheral
        self.A3 = 0.0  # mcg in slow peripheral
        self.Ce = 0.0  # effect-site (ng/mL)
        self.total_drug = 0.0  # cumulative drug (mcg)

    def reset(self) -> None:
        """Reset all compartments to zero."""
        self.A1 = 0.0
        self.A2 = 0.0
        self.A3 = 0.0
        self.Ce = 0.0
        self.total_drug = 0.0

    @property
    def plasma_concentration(self) -> float:
        """Plasma concentration in ng/mL (= mcg/L)."""
        return self.A1 / self.V1 if self.V1 > 0 else 0.0

    def step(self, infusion_rate_mcg_kg_min: float = 0.0) -> dict:
        """Advance one timestep with given infusion rate.

        Args:
            infusion_rate_mcg_kg_min: Remifentanil infusion rate in mcg/kg/min.

        Returns:
            Dict with plasma_concentration, effect_site_concentration.
        """
        # Convert to mcg/min total infusion
        R = infusion_rate_mcg_kg_min * self.weight_kg  # mcg/min
        self.total_drug += R * self.dt

        # Euler integration
        dA1 = (-(self.k10 + self.k12 + self.k13) * self.A1
               + self.k21 * self.A2 + self.k31 * self.A3 + R) * self.dt
        dA2 = (self.k12 * self.A1 - self.k21 * self.A2) * self.dt
        dA3 = (self.k13 * self.A1 - self.k31 * self.A3) * self.dt

        self.A1 = max(0.0, self.A1 + dA1)
        self.A2 = max(0.0, self.A2 + dA2)
        self.A3 = max(0.0, self.A3 + dA3)

        # Effect-site compartment
        Cp = self.plasma_concentration
        dCe = self.ke0 * (Cp - self.Ce) * self.dt
        self.Ce = max(0.0, self.Ce + dCe)

        return {
            "plasma_concentration": Cp,
            "effect_site_concentration": self.Ce,
        }

    def apply_variability(self, rng: np.random.Generator, scale: float = 0.2) -> None:
        """Apply inter-individual variability to PK parameters."""
        for attr in ("V1", "V2", "V3", "Cl1", "Cl2", "Cl3", "ke0"):
            base = getattr(self, attr)
            multiplier = 1.0 + rng.uniform(-scale, scale)
            setattr(self, attr, max(0.001, base * multiplier))
        # Recompute micro-constants
        self.k10 = self.Cl1 / self.V1
        self.k12 = self.Cl2 / self.V1
        self.k21 = self.Cl2 / self.V2
        self.k13 = self.Cl3 / self.V1
        self.k31 = self.Cl3 / self.V3
