import traceback

import pytest

from crawlee.statistics._error_tracker import ErrorTracker


@pytest.mark.parametrize(
    ('error_tracker', 'expected_unique_errors'),
    [
        (ErrorTracker(), 5),
        (ErrorTracker(show_file_and_line_number=False), 4),
        (ErrorTracker(show_error_name=False), 4),
        (ErrorTracker(show_error_message=False), 3),
        (ErrorTracker(show_error_name=False, show_file_and_line_number=False), 3),
        (ErrorTracker(show_file_and_line_number=False, show_error_message=False), 2),
        (ErrorTracker(show_error_name=False, show_file_and_line_number=False, show_error_message=False), 1),
    ],
)
async def test_error_tracker_counts(error_tracker: ErrorTracker, expected_unique_errors: int) -> None:
    """Use different settings of `error_tracker` and test unique errors count."""

    for error in [
        Exception('Some value error abc'),
        ValueError('Some value error abc'),  # Different type, different error
        ValueError('Some value error cde'),  # Same type and similar message to previous, considered the same.
        ValueError(
            'Another value error efg'
        ),  # Same type, but too different message to previous, considered different.
        ValueError(),  # Same type but don't have message, considered different.
    ]:
        try:
            raise error  # Errors raised on same line
        except Exception as e:  # noqa:PERF203
            await error_tracker.add(e)

    try:
        raise ValueError('Some value error abc')  # Same as one previous error, but different line.
    except Exception as e:
        await error_tracker.add(e)

    assert error_tracker.total == 6
    assert error_tracker.unique_error_count == expected_unique_errors


@pytest.mark.parametrize(
    ('message_1', 'message_2', 'expected_generic_message'),
    [
        ('Some error number 123', 'Some error number 456', 'Some error number ***'),
        ('Some error number 123 456', 'Some error number 123 456 789', 'Some error number 123 456 ***'),
        ('Some error number 0 0 0', 'Some error number 1 0 1', 'Some error number *** 0 ***'),
    ],
)
async def test_error_tracker_similar_messages_full_stack(
    message_1: str, message_2: str, expected_generic_message: str
) -> None:
    """Test that similar messages collapse into same group with generic name that contains wildcard symbols."""
    error_tracker = ErrorTracker()
    for error in [
        KeyError(message_1),
        KeyError(message_1),
        KeyError(message_1),
        ValueError(message_1),
        ValueError(message_2),
        RuntimeError(message_2),
    ]:
        try:
            raise error  # Errors raised on the same line
        except Exception as e:  # noqa:PERF203
            await error_tracker.add(e)
            line = traceback.extract_tb(e.__traceback__)[0].lineno

    file_name = __file__.split('/')[-1]
    errors = error_tracker.get_most_common_errors()
    assert errors[0][0] == f'{file_name}:{line}:KeyError:{message_1}'
    assert errors[0][1] == 3
    assert errors[1][0] == f'{file_name}:{line}:ValueError:{expected_generic_message}'
    assert errors[1][1] == 2
    assert errors[2][0] == f'{file_name}:{line}:RuntimeError:{message_2}'
    assert errors[2][1] == 1


@pytest.mark.parametrize(
    ('show_full_message', 'expected_message'),
    [
        (True, 'Error line 1\n Error line 2'),
        (False, 'Error line 1'),
    ],
)
async def test_show_full_message(*, show_full_message: bool, expected_message: str) -> None:
    """Test error message settings with both options of `show_full_message`."""
    error_tracker = ErrorTracker(
        show_error_name=False, show_file_and_line_number=False, show_full_message=show_full_message
    )

    try:
        raise RuntimeError('Error line 1\n Error line 2')  # Errors raised on the same line
    except Exception as e:
        await error_tracker.add(e)

    assert error_tracker.get_most_common_errors()[0][0] == expected_message


async def test_error_tracker_with_errors_chain() -> None:
    """Test error tracker with errors chain."""
    error_tracker = ErrorTracker(show_error_name=False, show_file_and_line_number=False, show_full_message=True)

    try:
        raise ZeroDivisionError('Zero division error')  # Errors raised on the same line
    except Exception as e:
        try:
            raise ValueError from e
        except Exception as e:
            await error_tracker.add(e)

    assert error_tracker.get_most_common_errors()[0][0] == 'Zero division error'
