"""Tests for PatientProfile and patient creation."""

import pytest
import numpy as np

from nephrosim.models.patient import (
    PatientProfile,
    create_patient,
    validate_parameter,
    PARAMETER_RANGES,
)


class TestPatientProfile:
    def test_default_patient(self):
        p = PatientProfile()
        assert p.body_weight == 75.0
        assert p.age == 60

    def test_total_body_water(self):
        p = PatientProfile(body_weight=80.0, urea_distribution_volume_fraction=0.6)
        assert abs(p.total_body_water - 48.0) < 0.01

    def test_ecf_icf_split(self):
        p = PatientProfile()
        assert abs(p.ecf_volume + p.icf_volume - p.total_body_water) < 0.01
        assert abs(p.ecf_volume - p.total_body_water / 3.0) < 0.01

    def test_max_uf_rate(self):
        p = PatientProfile(body_weight=75.0)
        expected = PARAMETER_RANGES["max_uf_rate_per_kg"]["default"] * 75.0
        assert abs(p.max_uf_rate - expected) < 0.01

    def test_invalid_urea_generation_rate(self):
        with pytest.raises(ValueError, match="urea_generation_rate"):
            PatientProfile(urea_generation_rate=50.0)

    def test_invalid_koa(self):
        with pytest.raises(ValueError, match="koa_urea"):
            PatientProfile(koa_urea=5000.0)

    def test_invalid_volume_fraction(self):
        with pytest.raises(ValueError, match="urea_distribution_volume_fraction"):
            PatientProfile(urea_distribution_volume_fraction=0.9)


class TestCreatePatient:
    def test_easy_deterministic(self):
        p = create_patient("easy")
        assert p.body_weight == 75.0
        assert p.noise_std == 0.0
        assert not p.is_diabetic

    def test_medium_has_variation(self):
        rng = np.random.default_rng(42)
        p1 = create_patient("medium", rng)
        rng2 = np.random.default_rng(99)
        p2 = create_patient("medium", rng2)
        assert p1.body_weight != p2.body_weight

    def test_hard_is_diabetic(self):
        p = create_patient("hard", np.random.default_rng(42))
        assert p.is_diabetic
        assert p.noise_std > 0

    def test_unknown_difficulty(self):
        with pytest.raises(ValueError, match="Unknown difficulty"):
            create_patient("impossible")

    def test_medium_weight_range(self):
        for seed in range(20):
            p = create_patient("medium", np.random.default_rng(seed))
            assert 60 <= p.body_weight <= 95

    def test_hard_weight_range(self):
        for seed in range(20):
            p = create_patient("hard", np.random.default_rng(seed))
            assert 55 <= p.body_weight <= 100

    def test_all_difficulties_produce_valid_patients(self):
        for diff in ["easy", "medium", "hard"]:
            p = create_patient(diff, np.random.default_rng(42))
            assert p.total_body_water > 0
            assert p.ecf_volume > 0
            assert p.icf_volume > 0
            assert p.max_uf_rate > 0


class TestParameterValidation:
    def test_valid_parameter(self):
        validate_parameter("blood_flow_rate", 350)

    def test_parameter_too_low(self):
        with pytest.raises(ValueError):
            validate_parameter("blood_flow_rate", 50)

    def test_parameter_too_high(self):
        with pytest.raises(ValueError):
            validate_parameter("blood_flow_rate", 1000)

    def test_unknown_parameter_passes(self):
        validate_parameter("unknown_param", 999)

    def test_all_defaults_valid(self):
        for name, p in PARAMETER_RANGES.items():
            validate_parameter(name, p["default"])
