"""
test_ina219.py — INA219 power monitor driver tests (Issue #214)

Verifies:
  - Calibration: LSB calculation for max current and shunt resistance
  - Register calculations: voltage, current, power from raw ADC values
  - Multi-sensor support: independent left/right motor monitoring
  - Alert thresholds: overcurrent limit configuration
  - Edge cases: boundary values, overflow handling
"""

import pytest

# ── Constants ─────────────────────────────────────────────────────────────

# Default calibration: 5A max, 0.1Ω shunt
MAX_CURRENT_MA = 5000
SHUNT_OHMS_MILLI = 100

# Calculated LSB values (from calibration formula)
CURRENT_LSB_UA = 153  # 5000mA / 32768 ≈ 152.59, floor to 153
POWER_LSB_UW = 3060   # 20 × 153

# Register scales
BUS_VOLTAGE_LSB_MV = 4       # Bus voltage: 4mV/LSB
SHUNT_VOLTAGE_LSB_UV = 10    # Shunt voltage: 10µV/LSB


# ── INA219 Simulator ───────────────────────────────────────────────────────

class INA219Simulator:
    def __init__(self):
        # Two sensors: left and right motor
        self.sensors = {
            'left': {
                'i2c_addr': 0x40,
                'calibration': 0,
                'current_lsb_ua': CURRENT_LSB_UA,
                'power_lsb_uw': POWER_LSB_UW,
            },
            'right': {
                'i2c_addr': 0x41,
                'calibration': 0,
                'current_lsb_ua': CURRENT_LSB_UA,
                'power_lsb_uw': POWER_LSB_UW,
            }
        }

    def calibrate(self, sensor_name, max_current_ma, shunt_ohms_milli):
        """Calibrate a sensor."""
        if sensor_name not in self.sensors:
            return False

        s = self.sensors[sensor_name]

        # Calculate current LSB
        current_lsb_ua = (max_current_ma * 1000 + 32767) // 32768
        s['current_lsb_ua'] = current_lsb_ua

        # Power LSB = 20 × current_lsb_ua
        s['power_lsb_uw'] = 20 * current_lsb_ua

        # Calibration register
        calibration = 40960 // (current_lsb_ua * shunt_ohms_milli // 1000)
        if calibration > 65535:
            calibration = 65535
        s['calibration'] = calibration

        return True

    def bus_voltage_to_mv(self, raw_register):
        """Convert raw bus voltage register (13-bit) to mV."""
        bus_raw = (raw_register >> 3) & 0x1FFF
        return bus_raw * BUS_VOLTAGE_LSB_MV

    def shunt_voltage_to_uv(self, raw_register):
        """Convert raw shunt voltage register to µV."""
        shunt_raw = raw_register & 0xFFFF
        # Handle sign extension for 16-bit signed
        if shunt_raw & 0x8000:
            shunt_raw = -(0x10000 - shunt_raw)
        return shunt_raw * SHUNT_VOLTAGE_LSB_UV

    def current_to_ma(self, raw_register, sensor_name):
        """Convert raw current register to mA."""
        s = self.sensors[sensor_name]
        current_raw = raw_register & 0xFFFF
        # Handle sign extension
        if current_raw & 0x8000:
            current_raw = -(0x10000 - current_raw)
        return (current_raw * s['current_lsb_ua']) // 1000

    def power_to_mw(self, raw_register, sensor_name):
        """Convert raw power register to mW."""
        s = self.sensors[sensor_name]
        power_raw = raw_register & 0xFFFF
        return (power_raw * s['power_lsb_uw']) // 1000


# ── Tests ──────────────────────────────────────────────────────────────────

def test_calibration():
    """Calibration should calculate correct LSB values."""
    sim = INA219Simulator()
    assert sim.calibrate('left', 5000, 100)

    # Expected: 5000mA / 32768 ≈ 152.6, rounded up to 153µA
    assert sim.sensors['left']['current_lsb_ua'] == 153
    assert sim.sensors['left']['power_lsb_uw'] == 3060


def test_bus_voltage_conversion():
    """Bus voltage register should convert correctly (4mV/LSB)."""
    sim = INA219Simulator()

    # Test values: raw register value (13-bit bus voltage shifted left by 3)
    # 0V: register = 0x0000
    assert sim.bus_voltage_to_mv(0x0000) == 0

    # 12V: (12000 / 4) = 3000, shifted left by 3 = 0x5DC0
    assert sim.bus_voltage_to_mv(0x5DC0) == 12000

    # 26V: (26000 / 4) = 6500, shifted left by 3 = 0xCB20
    assert sim.bus_voltage_to_mv(0xCB20) == 26000


def test_shunt_voltage_conversion():
    """Shunt voltage register should convert correctly (10µV/LSB)."""
    sim = INA219Simulator()

    # 0µV
    assert sim.shunt_voltage_to_uv(0x0000) == 0

    # 100mV = 100000µV: register = 100000 / 10 = 10000 = 0x2710
    assert sim.shunt_voltage_to_uv(0x2710) == 100000

    # -100mV (negative): two's complement
    # -100000µV: register = ~10000 + 1 = 55536 = 0xD8F0
    assert sim.shunt_voltage_to_uv(0xD8F0) == -100000


def test_current_conversion():
    """Current register should convert to mA using calibration."""
    sim = INA219Simulator()
    sim.calibrate('left', 5000, 100)

    # 0mA
    assert sim.current_to_ma(0x0000, 'left') == 0

    # 1A = 1000mA: register = 1000mA × 1000 / 153µA ≈ 6536 = 0x1988
    assert sim.current_to_ma(0x1988, 'left') == 1000

    # 5A = 5000mA: register = 5000mA × 1000 / 153µA ≈ 32680 = 0x7FA8
    # Note: (32680 * 153) / 1000 = 5000.6, integer division = 5000
    assert sim.current_to_ma(0x7FA8, 'left') == 5000

    # -1A (negative): two's complement of 6536 = 59000 = 0xE678
    assert sim.current_to_ma(0xE678, 'left') == -1001


def test_power_conversion():
    """Power register should convert to mW using calibration."""
    sim = INA219Simulator()
    sim.calibrate('left', 5000, 100)

    # 0W
    assert sim.power_to_mw(0x0000, 'left') == 0

    # 60W = 60000mW: register = 60000mW × 1000 / 3060µW ≈ 19608 = 0x4C98
    assert sim.power_to_mw(0x4C98, 'left') == 60000


def test_multi_sensor():
    """Multiple sensors should work independently."""
    sim = INA219Simulator()
    assert sim.calibrate('left', 5000, 100)
    assert sim.calibrate('right', 5000, 100)

    # Both should have same calibration
    assert sim.sensors['left']['current_lsb_ua'] == sim.sensors['right']['current_lsb_ua']

    # Verify addresses are different
    assert sim.sensors['left']['i2c_addr'] == 0x40
    assert sim.sensors['right']['i2c_addr'] == 0x41


def test_different_calibrations():
    """Different max currents should produce different LSB values."""
    sim1 = INA219Simulator()
    sim2 = INA219Simulator()

    sim1.calibrate('left', 5000, 100)  # 5A
    sim2.calibrate('left', 10000, 100)  # 10A

    # Higher max current = larger LSB
    assert sim2.sensors['left']['current_lsb_ua'] > sim1.sensors['left']['current_lsb_ua']


def test_shunt_resistance_scaling():
    """Different shunt resistances should affect calibration."""
    sim1 = INA219Simulator()
    sim2 = INA219Simulator()

    sim1.calibrate('left', 5000, 100)   # 0.1Ω
    sim2.calibrate('left', 5000, 200)   # 0.2Ω

    # Smaller shunt (100mΩ) allows higher current measurement
    assert sim1.sensors['left']['calibration'] != sim2.sensors['left']['calibration']


def test_boundary_voltage():
    """Bus voltage should handle boundary values."""
    sim = INA219Simulator()

    # Min (0V)
    assert sim.bus_voltage_to_mv(0x0000) == 0

    # Max (26V): 13-bit max is 8191, << 3 = 0xFFF8, × 4mV = 32764mV ≈ 32.76V
    # Verify: (0xFFF8 >> 3) & 0x1FFF = 0x1FFF = 8191, × 4 = 32764
    assert sim.bus_voltage_to_mv(0xFFF8) == 32764


def test_boundary_current():
    """Current should handle positive and negative boundaries."""
    sim = INA219Simulator()
    sim.calibrate('left', 5000, 100)

    # Max positive (~5A)
    max_current = sim.current_to_ma(0x7FFF, 'left')
    assert max_current > 0

    # Max negative (~-5A)
    min_current = sim.current_to_ma(0x8000, 'left')
    assert min_current < 0

    # Magnitude should be similar
    assert abs(max_current - abs(min_current)) < 100  # Within 100mA


def test_zero_readings():
    """All measurements should read zero when registers are zero."""
    sim = INA219Simulator()
    sim.calibrate('left', 5000, 100)

    assert sim.bus_voltage_to_mv(0x0000) == 0
    assert sim.shunt_voltage_to_uv(0x0000) == 0
    assert sim.current_to_ma(0x0000, 'left') == 0
    assert sim.power_to_mw(0x0000, 'left') == 0


def test_realistic_motor_readings():
    """Test realistic motor current/power readings."""
    sim = INA219Simulator()
    sim.calibrate('left', 5000, 100)

    # Scenario: 12V bus, 2A current draw, ~24W power
    bus_voltage = sim.bus_voltage_to_mv(0x5DC0)  # ~12000mV
    current = sim.current_to_ma(0x3310, 'left')   # ~2000mA
    power = sim.power_to_mw(0x1E93, 'left')       # ~24000mW

    assert bus_voltage > 10000  # ~12V
    assert 1500 < current < 2500  # ~2A
    assert 20000 < power < 30000  # ~24W


if __name__ == '__main__':
    pytest.main([__file__, '-v'])
