from mhs5200 import MHS5200, Waveform, Attenuation, SweepMode
from mhs5200.exceptions import ResponseError, CommunicationError
from unittest.mock import call
import pytest
    
# =========================================================================
# Device Info
# =========================================================================
def test_get_device_info(mhs, mock_serial):
    """
    The response contains both model and serial concatenated, requiring precise slicing.
    """
    mock_serial.readline.return_value = b':r0c5200A12345\n'
    
    info = mhs.get_device_info()
    
    assert info['model'] == 'MHS-5200A'
    assert info['serial'] == '12345'
    mock_serial.write.assert_called_with(b':r0c\n')
    
# =========================================================================
# Frequency - Conversion Logic
# =========================================================================
def test_set_frequency_conversion(mhs, mock_serial):
    """
    Device expects frequency in 0.01Hz units (e.g., 1000.50Hz -> 100050).
    Incorrect conversion leads to wrong output frequency.
    """
    mock_serial.readline.return_value = b'ok\n'
    
    mhs.set_frequency(1, 1000.50)
    
    mock_serial.write.assert_called_with(b':s1f100050\n')

# =========================================================================
# Amplitude - Dependency on Attenuation
# =========================================================================
def test_set_amplitude_0db_mode(mhs, mock_serial):
    """
    In 0dB mode, the device uses 10mV units.
    Mismatch between mode and scaling causes 10x amplitude error.
    """
    mock_serial.readline.side_effect = [
        b':r1y1\n',
        b'ok\n'
    ]
    
    mhs.set_amplitude(1, 5.0)
    
    expected_calls = [
        call(b':r1y\n'),
        call(b':s1a500\n')
    ]
    mock_serial.write.assert_has_calls(expected_calls)

def test_set_amplitude_20db_mode(mhs, mock_serial):
    """
    In -20dB mode, the device uses 1mV units.
    """
    mock_serial.readline.side_effect = [
        b':r1y0\n',
        b'ok\n'
    ]
    
    mhs.set_amplitude(1, 0.5)
    
    expected_calls = [
        call(b':r1y\n'),
        call(b':s1a500\n')
    ]
    mock_serial.write.assert_has_calls(expected_calls)

# =========================================================================
# Waveform - Read/Write Asymmetry
# =========================================================================
def test_set_waveform_arb_mapping(mhs, mock_serial):
    """
    Arbitrary waveforms use index 100+ for writing (ARB00 = 100).
    """
    mock_serial.readline.return_value = b'ok\n'
    
    mhs.set_waveform(1, Waveform.ARB00)
    
    mock_serial.write.assert_called_with(b':s1w100\n')

def test_get_waveform_read_conversion(mhs, mock_serial):
    """
    The device returns 10 for ARB00 on read, but expects 100 on write.
    This asymmetry must be handled.
    """
    mock_serial.readline.return_value = b':r1w10\n'
    
    wf = mhs.get_waveform(1)
    
    assert wf == Waveform.ARB00
    assert wf.value == 100

# =========================================================================
# Phase - Boundary Handling
# =========================================================================
def test_set_phase_validation(mhs, mock_serial):
    """
    Hardware supports -180 to +180 degrees.
    Values outside this range should be rejected before sending to the device.
    """
    with pytest.raises(ValueError):
        mhs.set_phase(181)
    
    with pytest.raises(ValueError):
        mhs.set_phase(-181)

    mock_serial.readline.return_value = b'ok\n'
    mhs.set_phase(180)
    mock_serial.write.assert_called_with(b':s1p180\n')

# =========================================================================
# Arbitrary Waveform - Complex Slicing Logic
# =========================================================================
def test_upload_arbitrary_waveform_slicing(mhs, mock_serial):
    """
    2048 samples must be split into 16 slices of 128 samples each.
    Indices are hex-formatted. Any slicing error corrupts the waveform.
    """
    mock_serial.readline.return_value = b'ok\n'
    
    samples = list(range(2048))
    channel_index = 10
    
    mhs.upload_arbitrary_waveform(channel_index, samples)
    
    assert mock_serial.write.call_count == 16
    
    args, _ = mock_serial.write.call_args_list[0]
    assert args[0].startswith(b':aA00,1,2,')
    
    args_last, _ = mock_serial.write.call_args_list[15]
    assert args_last[0].startswith(b':aAF1920,1921,')

# =========================================================================
# Sweep - Parameter Mapping
# =========================================================================
def test_configure_sweep_command_sequence(mhs, mock_serial):
    """
    configure_sweep abstracts 5 commands (start/stop freq, time, mode, channel).
    All parameters must be correctly converted and propagated.
    """
    mock_serial.readline.return_value = b'ok\n'
    
    mhs.configure_sweep(
        start_hz=100,
        stop_hz=1000,
        time_sec=10,
        mode=SweepMode.LOG,
        channel=2
    )
    
    expected_calls = [
        call(b':s3f10000\n'),
        call(b':s4f100000\n'),
        call(b':s1t10\n'),
        call(b':s7b1\n'),
        call(b':s2b2\n')
    ]
    
    mock_serial.write.assert_has_calls(expected_calls, any_order=True)
    
# =========================================================================
# Error Handling
# =========================================================================
def test_get_device_info_error_handling(mhs, mock_serial):
    """
    Incomplete responses must raise an exception to prevent silent failures.
    """
    mock_serial.readline.return_value = b':r0c12\n'
    
    with pytest.raises(ResponseError):
        mhs.get_device_info()