# coding: utf-8
import sys
from _pydevd_bundle.pydevd_constants import EXCEPTION_TYPE_USER_UNHANDLED
import pytest
from tests_python.debug_constants import IS_PY311_OR_GREATER


def test_create_frames_list_from_traceback():
    def method():
        raise RuntimeError("first")

    def method1():
        try:
            method()
        except Exception as e:
            raise RuntimeError("second") from e

    def method2():
        try:
            method1()
        except Exception as e:
            raise RuntimeError("third") from e

    try:
        method2()
    except Exception as e:
        exc_type, exc_desc, trace_obj = sys.exc_info()
        frame = sys._getframe()

        from _pydevd_bundle.pydevd_frame_utils import create_frames_list_from_traceback

        frames_list = create_frames_list_from_traceback(trace_obj, frame, exc_type, exc_desc, exception_type=EXCEPTION_TYPE_USER_UNHANDLED)
        assert str(frames_list.exc_desc) == "third"
        assert str(frames_list.chained_frames_list.exc_desc) == "second"
        assert str(frames_list.chained_frames_list.chained_frames_list.exc_desc) == "first"
        assert frames_list.chained_frames_list.chained_frames_list.chained_frames_list is None


if IS_PY311_OR_GREATER:
    import traceback

    _byte_offset_to_character_offset = getattr(traceback, "_byte_offset_to_character_offset", None)
    if _byte_offset_to_character_offset is not None:
        _original = traceback._byte_offset_to_character_offset

        def _byte_offset_to_character_offset(*args, **kwargs):
            try:
                return _original(*args, **kwargs)
            except:
                # Replacement to deal with the buggy version released on Python 3.11.0.
                def replacement(str, offset):
                    as_utf8 = str.encode("utf-8")
                    if offset > len(as_utf8):
                        offset = len(as_utf8)

                    return len(as_utf8[: offset + 1].decode("utf-8", "replace"))

                return replacement(*args, **kwargs)

        traceback._byte_offset_to_character_offset = _byte_offset_to_character_offset

_USE_UNICODE = [False, True]


@pytest.mark.parametrize("use_unicode", _USE_UNICODE)
@pytest.mark.skipif(not IS_PY311_OR_GREATER, reason="Python 3.11 required.")
def test_collect_anchors_subscript(use_unicode):
    from _pydevd_bundle.pydevd_frame_utils import create_frames_list_from_traceback

    if use_unicode:

        def method():
            d = {"x": {"á": {"í": {"theta": 1}}}}

            result = d["x"]["á"]["í"]["beta"]

    else:

        def method():
            d = {"x": {"y": {"i": {"theta": 1}}}}

            result = d["x"]["y"]["i"]["beta"]

    try:
        method()
    except:
        exc_type, exc_desc, trace_obj = sys.exc_info()
        memo = {}
        frame = None
        frames_list = create_frames_list_from_traceback(trace_obj, frame, exc_type, exc_desc, memo)
        iter_in = iter(frames_list)
        f = next(iter_in)
        assert f.f_code.co_name == "method"
        line_col_info = frames_list.frame_id_to_line_col_info[id(f)]

        if use_unicode:
            line = '            result = d["x"]["á"]["í"]["beta"]'
        else:
            line = '            result = d["x"]["y"]["i"]["beta"]'

        # Ok, so, the range that we we have covers >>d["x"]["á"]["í"]["beta"]<<
        # the problem here is that ideally we'd like to present to the user that
        # the current key is "beta", so, we need to do some additional computation
        # to find out the proper column to show to the user.
        # (see https://github.com/microsoft/debugpy/issues/1099
        # for more information).
        assert line_col_info.colno == line.index('d["x"]')

        # It's +1 here due to the í unicode char (we need to convert from the bytes
        # index to the actual character in the string to get the actual col).
        if use_unicode:
            assert line_col_info.end_colno == len(line) + 2
        else:
            assert line_col_info.end_colno == len(line)
        original_line = line

        col, endcol = line_col_info.map_columns_to_line(original_line)
        assert col == line.index('["beta"]')
        assert endcol == len(line)


@pytest.mark.parametrize("use_unicode", _USE_UNICODE)
@pytest.mark.skipif(not IS_PY311_OR_GREATER, reason="Python 3.11 required.")
def test_collect_anchors_binop_1(use_unicode):
    from _pydevd_bundle.pydevd_frame_utils import create_frames_list_from_traceback

    if use_unicode:

        def method():
            á = 1
            í = 2
            c = tuple

            result = á + í + c

    else:

        def method():
            a = 1
            b = 2
            c = tuple

            result = a + b + c

    try:
        method()
    except:
        exc_type, exc_desc, trace_obj = sys.exc_info()
        memo = {}
        frame = None
        frames_list = create_frames_list_from_traceback(trace_obj, frame, exc_type, exc_desc, memo)
        iter_in = iter(frames_list)
        f = next(iter_in)
        assert f.f_code.co_name == "method"
        line_col_info = frames_list.frame_id_to_line_col_info[id(f)]

        if use_unicode:
            line = "            result = á + í + c"
            expected_index = line.index("á + í")
        else:
            line = "            result = a + b + c"
            expected_index = line.index("a + b")

        assert line_col_info.colno == expected_index

        # It's +2 here due to the á and í unicode chars (we need to convert from the bytes
        # index to the actual character in the string to get the actual col).
        if use_unicode:
            assert line_col_info.end_colno == len(line) + 2
        else:
            assert line_col_info.end_colno == len(line)
        original_line = line

        col, endcol = line_col_info.map_columns_to_line(original_line)
        assert col == line.index("+ c")
        assert endcol == col + 1


@pytest.mark.parametrize("use_unicode", _USE_UNICODE)
@pytest.mark.skipif(not IS_PY311_OR_GREATER, reason="Python 3.11 required.")
def test_collect_anchors_binop_2(use_unicode):
    from _pydevd_bundle.pydevd_frame_utils import create_frames_list_from_traceback

    if use_unicode:

        def method():
            á = 1
            í = 2
            c = tuple

            result = á + c + í

    else:

        def method():
            a = 1
            b = 2
            c = tuple

            result = a + c + b

    try:
        method()
    except:
        exc_type, exc_desc, trace_obj = sys.exc_info()
        memo = {}
        frame = None
        frames_list = create_frames_list_from_traceback(trace_obj, frame, exc_type, exc_desc, memo)
        iter_in = iter(frames_list)
        f = next(iter_in)
        assert f.f_code.co_name == "method"
        line_col_info = frames_list.frame_id_to_line_col_info[id(f)]

        if use_unicode:
            line = "            result = á + c + í"
            expected_index = line.index("á + c")
        else:
            line = "            result = a + c + b"
            expected_index = line.index("a + c")

        assert line_col_info.colno == expected_index

        # It's +2 here due to the á and í unicode chars (we need to convert from the bytes
        # index to the actual character in the string to get the actual col).
        if use_unicode:
            assert line_col_info.end_colno == line.index("c + í") + 2
        else:
            assert line_col_info.end_colno == line.index("c + b") + 1
        original_line = line

        col, endcol = line_col_info.map_columns_to_line(original_line)
        assert col == 23
        assert endcol == 24
        assert col == line.index("+ c")
        assert endcol == col + 1
