# Exam 1 Tests - Problem 2 (10 of 20 points)
import pytest
import subprocess
import re
from math import isclose

basename = "p2"

def run_with_args(filename, *args):
    try:
        args = [str(a) for a in args]
        command = ["python3", filename + ".py", *args]
        return subprocess.check_output(command, text=True).rstrip("\n")
    except subprocess.CalledProcessError as e:
        print(f"\nProgram crashed with exit code {e.returncode}")
        if e.output:
            print(f"Output before crash: {e.output}")
        if e.stderr:
            print(f"Error message: {e.stderr}")
        raise AssertionError() from None

# Test cases: (amount, from_unit, expected_major, expected_minor_unit, expected_minor_value)
kg_to_lb_cases = [
    (1, "kg", 2, "oz", 3.2739),
    (10, "kg", 22, "oz", 0.7392),
    (0.5, "kg", 1, "oz", 1.63696),
    (2.5, "kg", 5, "oz", 8.248),
    (100, "kg", 220, "oz", 7.392)
]

lb_to_kg_cases = [
    (1, "lb", 0, "g", 453.592),
    (10, "lb", 4, "g", 535.92),
    (5, "lb", 2, "g", 267.96),
    (100, "lb", 45, "g", 359.2),
    (2.2, "lb", 0, "g", 997.9024)
]

# Basic format tests - Do outputs contain numbers and correct units?
@pytest.mark.parametrize("amount,from_unit,expected_major,expected_minor_unit,expected_minor", kg_to_lb_cases)
def test_basic_format(amount, from_unit, expected_major, expected_minor_unit, expected_minor):
    output = run_with_args(basename, amount, from_unit)
    
    # Check if output has the format "X unit Y unit"
    pattern = r'^\d+ \w+ \d+(\.\d+)? \w+$'
    assert re.match(pattern, output), f"Output format incorrect: {output}"
    
    # Check correct units in output
    if from_unit == "kg":
        assert "lb" in output and "oz" in output, f"Expected 'lb' and 'oz' in output for kg conversion: {output}"
    else:  # from_unit == "lb"
        assert "kg" in output and "g" in output, f"Expected 'kg' and 'g' in output for lb conversion: {output}"

# Major unit tests - Are the integer parts correct?
@pytest.mark.parametrize("amount,from_unit,expected_major,expected_minor_unit,expected_minor", kg_to_lb_cases)
def test_kg_to_lb_major_unit(amount, from_unit, expected_major, expected_minor_unit, expected_minor):
    output = run_with_args(basename, amount, from_unit)
    major_value = int(output.split()[0])
    assert major_value == expected_major, f"Expected {expected_major} lb, got {major_value} lb"

@pytest.mark.parametrize("amount,from_unit,expected_major,expected_minor_unit,expected_minor", lb_to_kg_cases)
def test_lb_to_kg_major_unit(amount, from_unit, expected_major, expected_minor_unit, expected_minor):
    output = run_with_args(basename, amount, from_unit)
    major_value = int(output.split()[0])
    assert major_value == expected_major, f"Expected {expected_major} kg, got {major_value} kg"

# Minor unit tests - Are the fractional parts correct?
@pytest.mark.parametrize("amount,from_unit,expected_major,expected_minor_unit,expected_minor", kg_to_lb_cases)
def test_kg_to_lb_minor_unit(amount, from_unit, expected_major, expected_minor_unit, expected_minor):
    output = run_with_args(basename, amount, from_unit)
    minor_value = float(output.split()[2])
    assert isclose(minor_value, expected_minor, rel_tol=0.01), f"Expected {expected_minor} oz, got {minor_value} oz"

# Full conversion tests - Is the entire output correct?
@pytest.mark.parametrize("amount,from_unit,expected_major,expected_minor_unit,expected_minor", kg_to_lb_cases)
def test_kg_to_lb_full(amount, from_unit, expected_major, expected_minor_unit, expected_minor):
    output = run_with_args(basename, amount, from_unit)
    actual_major = int(output.split()[0])
    actual_minor = float(output.split()[2])
    assert actual_major == expected_major, f"Major unit mismatch: {output}"
    assert isclose(actual_minor, expected_minor, rel_tol=0.01), f"Minor unit mismatch: {output}"

@pytest.mark.parametrize("amount,from_unit,expected_major,expected_minor_unit,expected_minor", lb_to_kg_cases)
def test_lb_to_kg_full(amount, from_unit, expected_major, expected_minor_unit, expected_minor):
    output = run_with_args(basename, amount, from_unit)
    actual_major = int(output.split()[0])
    actual_minor = float(output.split()[2])
    assert actual_major == expected_major, f"Major unit mismatch: {output}"
    assert isclose(actual_minor, expected_minor, rel_tol=0.01), f"Minor unit mismatch: {output}"

if __name__ == "__main__":
    import sys
    if "--one" in sys.argv:
        flags = "-xvs"
    else:
        flags = "-v"
    pytest.main(["p2_test.py", flags,  "-p", "no:faulthandler"])
