import unittest, math
from dataclasses import replace
from model import Case, simulate, ideal_duration, operating_point, transfer

class BackupTests(unittest.TestCase):
    def test_zero_esr_matches_independent_energy_solution(self):
        for c in [Case(esr_ohm=0),Case(esr_ohm=0,capacitance_f=3,efficiency=.78,initial_v=21)]:
            self.assertAlmostEqual(simulate(c)['duration_s'],ideal_duration(c),places=6)
    def test_nonlinear_energy_conservation(self):
        r=simulate(Case());self.assertLess(abs(r['energy_residual_j']),1e-6)
        self.assertGreater(r['esr_loss_j'],0)
    def test_esr_reduces_runtime_below_ideal_bound(self):
        c=Case(esr_ohm=.5);self.assertLess(simulate(c)['duration_s'],ideal_duration(c))
    def test_degradation_reduces_runtime(self):
        baseline=simulate(Case())['duration_s']
        for changes in [dict(capacitance_f=8),dict(esr_ohm=.36),dict(initial_v=21.6),dict(efficiency=.82)]:
            self.assertLess(simulate(replace(Case(),**changes))['duration_s'],baseline)
    def test_worst_reference_case_passes_recording_and_shutdown(self):
        c=Case(capacitance_f=5.76,esr_ohm=.36,initial_v=21.6,efficiency=.82)
        self.assertGreater(simulate(c)['duration_s'],125)
    def test_undersized_bank_is_detected(self):
        self.assertLess(simulate(Case(capacitance_f=2))['duration_s'],125)
    def test_current_limit_ends_discharge(self):
        r=simulate(Case(current_limit_a=.6));self.assertEqual(r['cutoff_reason'],'input current limit')
        self.assertAlmostEqual(r['maximum_input_current_a'],.6,places=8)
    def test_current_root_satisfies_power_balance(self):
        for v in [12,18,24]:
            i,terminal=operating_point(v,8,.4);self.assertAlmostEqual(i*terminal,8,places=12)
    def test_time_step_refinement(self):
        c=Case(capacitance_f=5.76,esr_ohm=.36,initial_v=21.6,efficiency=.82)
        self.assertLess(abs(simulate(c,.5)['duration_s']-simulate(c,.125)['duration_s']),1e-6)
    def test_transfer_zero_esr_matches_energy_equation(self):
        v=transfer(resistance=0);self.assertAlmostEqual(v,math.sqrt(24**2-2*12*.002/(.0047*.8)),places=9)
    def test_transfer_capacity_and_gap_sensitivity(self):
        self.assertGreater(transfer(),21.6)
        self.assertLess(transfer(capacitance_f=.00047),transfer())
        self.assertLess(transfer(gap_s=.01),transfer())
    def test_invalid_inputs_and_impossible_start_rejected(self):
        for c in [Case(capacitance_f=0),Case(efficiency=1.1),Case(initial_v=9),Case(current_limit_a=.1),Case(esr_ohm=100)]:
            with self.assertRaises(ValueError):simulate(c)
        with self.assertRaises(ValueError):simulate(Case(),0)

if __name__=='__main__':unittest.main()
