"""Tests for ScreenQA utility functions."""

from screensuite.benchmarks.utils import (
    BoundingBox,
    NormalizedClickCoordinates,
    compute_exact,
    compute_f1,
    evaluate_bounding_box_action,
    evaluate_click_distance,
    evaluate_click_in_bounding_box,
    evaluate_type_action,
    normalize_answer,
    parse_bounding_box_action,
    parse_click_action,
    parse_type_action,
)


def test_normalize_squad():
    # Test basic normalization
    assert normalize_answer("The Quick Brown Fox") == "quick brown fox"

    # Test with punctuation
    assert normalize_answer("Hello, World!") == "hello world"

    # Test with extra whitespace
    assert normalize_answer("multiple    spaces") == "multiple spaces"

    # Test with articles
    assert normalize_answer("a cat and the dog") == "cat and dog"


def test_f1_score():
    # Test exact match
    pred = "hello world"
    gold = "hello world"
    assert compute_f1(pred, gold) == 1.0

    # Test partial match
    pred = "hello there"
    gold = "hello world"
    expected_f1 = 0.5
    assert compute_f1(pred, gold) == expected_f1

    # Test no match
    pred = "goodbye world"
    gold = "hello world"
    expected_f1 = 0.5
    assert compute_f1(pred, gold) == expected_f1

    # Test empty lists
    assert compute_f1("", "") == 1
    assert compute_f1("hello", "") == 0
    assert compute_f1("", "hello") == 0


def test_compute_exact():
    # Test exact match
    pred = "Hello World"
    gold = "Hello World"
    exact_match = compute_exact(pred, gold)
    assert exact_match == 1

    # Test multiple ground truths
    pred = "Hello World"
    gold = "Hello World"
    exact_match = compute_exact(pred, gold)
    assert exact_match == 1

    pred = "Hello World"
    gold = "Hello There"
    exact_match = compute_exact(pred, gold)
    assert exact_match == 0

    pred = "HelLo WoRLd"
    gold = "hello world"
    exact_match = compute_exact(pred, gold)
    assert exact_match == 1


def test_parse_click_action():
    """Test click coordinate parsing with multiple formats"""

    test_cases = [
        # Standard format - normalized coordinates
        ("click(0.5, 0.75)", 1, 1, (0.5, 0.75)),
        ("click( 0.5 , 0.75 )", 1, 1, (0.5, 0.75)),
        # Standard format - pixel coordinates
        ("click(100, 200)", 1000, 1000, (0.1, 0.2)),
        ("Click(100, 200)", 1000, 1000, (0.1, 0.2)),  # Case insensitive
        ("click( 100.5 , 200.75 )", 1000, 1000, (0.1005, 0.20075)),
        ("click(264,954)", 1200, 1000, (0.22, 0.954)),
        # UITars format
        ("click(point='<point>100 200</point>')", 1000, 1000, (0.1, 0.2)),
        ("Click(point='<point>100 200</point>')", 1000, 1000, (0.1, 0.2)),  # Case insensitive
        ('click(point="<point>100 200</point>")', 1000, 1000, (0.1, 0.2)),  # Double quotes
        ("click(point = '<point>100.5 200.75</point>')", 1000, 1000, (0.1005, 0.20075)),  # Extra spaces
        # start_box format
        ("click(start_box='(537,212)')", 1000, 1000, (0.537, 0.212)),
        ("click(start_box='(537.0, 212.0)')", 1000, 1000, (0.537, 0.212)),  # With space
        ('click(start_box="(537,212)")', 1000, 1000, (0.537, 0.212)),  # Double quotes
        # Other variations that should work with the simplified parser
        ("click(x=100, y=200)", 1000, 1000, (0.1, 0.2)),
        ("click(coordinates: 100, 200)", 1000, 1000, (0.1, 0.2)),
        ("click(position=[100, 200])", 1000, 1000, (0.1, 0.2)),
        # Invalid formats
        ("click(100)", 1000, 1000, None),  # Only one number
        ("click(a, b)", 1000, 1000, None),  # Non-numeric coordinates
        ("click()", 1000, 1000, None),  # No coordinates
        ("type(hello)", 1000, 1000, None),  # Different action type
        ("", 1, 1, None),  # Empty string
    ]

    for prediction, width, height, expected in test_cases:
        result = parse_click_action(prediction, width, height)
        if result is not None:
            coords = result.to_xy()
            coords = (round(coords[0], 5), round(coords[1], 5))  # Round for comparison
        else:
            coords = None

        assert coords == expected, f"Failed for {prediction}: got {coords}, expected {expected}"


def test_parse_bounding_box_action():
    # Test valid bounding box action
    pred = "boundingbox(0.5, 0.75, 0.7, 0.95)"
    result = parse_bounding_box_action(pred)
    assert result is not None
    x1, y1, x2, y2 = result.to_xyxy()
    assert x1 == 0.5
    assert y1 == 0.75
    assert x2 == 0.7
    assert y2 == 0.95

    # Test with extra spaces
    pred = "boundingbox( 100.5 , 200.75 , 300.25 , 400.5 )"
    result = parse_bounding_box_action(pred, image_width=1000, image_height=1000)
    assert result is not None
    x1, y1, x2, y2 = result.to_xyxy()
    assert x1 == 0.1005
    assert y1 == 0.20075
    assert x2 == 0.30025
    assert y2 == 0.4005

    # Test invalid format
    pred = "boundingbox(100.5, 200.75, 300.25)"
    assert parse_bounding_box_action(pred) is None

    # Test empty string
    assert parse_bounding_box_action("") is None


def test_evaluate_click_in_bounding_box():
    # Test click inside bounding box
    pred = "click(0.5, 0.75)"
    target_bbox = BoundingBox.from_xyxy(xyxy_bbox=(0.0, 0.0, 1.0, 1.0))
    assert evaluate_click_in_bounding_box(pred, target_bbox) == 1.0

    # Test click outside bounding box
    pred = "click(0.5, 0.75)"
    target_bbox = BoundingBox.from_xyxy(xyxy_bbox=(0.0, 0.0, 0.4, 0.6))
    assert evaluate_click_in_bounding_box(pred, target_bbox) == 0.0

    # Test click with string
    pred = "click('a', 'b')"
    assert parse_click_action(pred) is None

    # Test invalid click format
    pred = "click(150)"
    target_bbox = BoundingBox.from_xyxy(xyxy_bbox=(0.0, 0.0, 1.0, 1.0))
    assert evaluate_click_in_bounding_box(pred, target_bbox) == 0.0


def test_evaluate_click_distance():
    pred = "click(0.5, 0.75)"
    target_coordinates = NormalizedClickCoordinates(x=0.5, y=0.75)
    assert evaluate_click_distance(pred, target_coordinates) == 1.0

    pred = "click(0.5, 0.25)"
    target_coordinates = NormalizedClickCoordinates(x=1, y=1)
    assert evaluate_click_distance(pred, target_coordinates) == 0.0


def test_evaluate_bounding_box_action():
    # Test perfect overlap
    pred = "boundingbox(0.1, 0.2, 0.3, 0.4)"
    target_bbox = BoundingBox.from_xyxy(xyxy_bbox=(0.1, 0.2, 0.3, 0.4))
    assert evaluate_bounding_box_action(pred, target_bbox) == 1.0

    # Test partial overlap
    pred = "boundingbox(0.15, 0.25, 0.35, 0.45)"
    target_bbox = BoundingBox.from_xyxy(xyxy_bbox=(0.1, 0.2, 0.3, 0.4))
    # Intersection area: 150x150 = 22500
    # Union area: 200x200 + 200x200 - 22500 = 57500
    # IoU = 22500/57500 ≈ 0.3913
    assert abs(evaluate_bounding_box_action(pred, target_bbox) - 0.3913) < 0.0001

    # Test no overlap
    pred = "boundingbox(0.4, 0.5, 0.6, 0.7)"
    target_bbox = BoundingBox.from_xyxy(xyxy_bbox=(0.1, 0.2, 0.3, 0.4))
    assert evaluate_bounding_box_action(pred, target_bbox) == 0.0

    # Test invalid bounding box format
    pred = "boundingbox(0.1, 0.2, 0.3)"
    target_bbox = BoundingBox.from_xyxy(xyxy_bbox=(0.1, 0.2, 0.3, 0.4))
    assert evaluate_bounding_box_action(pred, target_bbox) == 0.0


def test_parse_type_action():
    # Test valid type action
    pred = "type(hello world)"
    result = parse_type_action(pred)
    assert result == "hello world"

    pred = 'type("hello world")'
    result = parse_type_action(pred)
    assert result == "hello world"

    pred = 'type("hello world")'
    result = parse_type_action(pred)
    assert result == "hello world"

    # Test with extra spaces
    pred = "type(  hello world  )"
    result = parse_type_action(pred)
    assert result == "hello world"

    pred = 'type( " hello world " )'
    result = parse_type_action(pred)
    assert result == "hello world"

    pred = 'type(" hello world ")'
    result = parse_type_action(pred)
    assert result == "hello world"

    pred = "type(content='hello world')"
    result = parse_type_action(pred)
    assert result == "hello world"

    # Test with mixed case (should be converted to lowercase)
    pred = 'type("Hello World")'
    result = parse_type_action(pred)
    assert result == "hello world"

    # Test invalid format
    pred = "type(hello world)"
    assert parse_type_action(pred) is not None

    # Test empty string
    assert parse_type_action("") is None

    # Test missing parentheses
    pred = "type hello world"
    assert parse_type_action(pred) is None

    # Test multiple matches
    pred = "type(hello)type(world)"
    result = parse_type_action(pred)
    assert result == "hello"


def test_evaluate_type_action():
    # Test exact match
    pred = "type(hello world)"
    gold = "hello world"
    accuracy = evaluate_type_action(pred, gold)
    assert accuracy == 1.0

    # Test partial match
    pred = "type(hello there)"
    gold = "hello world"
    accuracy = evaluate_type_action(pred, gold)
    assert accuracy == 0.0

    # Test with extra spaces
    pred = "type(  hello world  )"
    gold = "hello world"
    accuracy = evaluate_type_action(pred, gold)
    assert accuracy == 1.0

    # Test with mixed case
    pred = "type(Hello World)"
    gold = "hello world"
    accuracy = evaluate_type_action(pred, gold)
    assert accuracy == 1.0

    # Test invalid format
    pred = "type(hello, world)"
    gold = "hello world"
    accuracy = evaluate_type_action(pred, gold)
    assert accuracy == 0.0

    # Test empty string
    pred = ""
    gold = "hello world"
    accuracy = evaluate_type_action(pred, gold)
    assert accuracy == 0.0

    # Test missing parentheses
    pred = "type hello world"
    gold = "hello world"
    accuracy = evaluate_type_action(pred, gold)
    assert accuracy == 0.0
