import builtins
import os
import sys
from unittest.mock import MagicMock

import pytest

from loguru import logger
from loguru._colorama import should_colorize, should_wrap

from .conftest import (
    StreamFilenoException,
    StreamIsattyException,
    StreamIsattyFalse,
    StreamIsattyTrue,
)


@pytest.fixture(autouse=True)
def clear_environment():
    env = os.environ.copy()
    os.environ.clear()
    yield
    os.environ.update(env)


@pytest.fixture
def patch_colorama(monkeypatch):
    ansi_to_win32_class = MagicMock()
    winapi_test = MagicMock(return_value=True)
    enable_vt_processing = MagicMock(return_value=False)
    win32 = MagicMock(winapi_test=winapi_test)
    winterm = MagicMock(enable_vt_processing=enable_vt_processing)
    colorama = MagicMock(AnsiToWin32=ansi_to_win32_class, win32=win32, winterm=winterm)
    with monkeypatch.context() as context:
        context.setitem(sys.modules, "colorama", colorama)
        context.setitem(sys.modules, "colorama.win32", win32)
        context.setitem(sys.modules, "colorama.winterm", winterm)
        yield colorama


@pytest.mark.parametrize("patched", ["__stdout__", "__stderr__"])
@pytest.mark.skipif(os.name != "nt", reason="Only Windows requires Colorama")
def test_stream_wrapped_on_windows_if_no_vt_support(patched, monkeypatch, patch_colorama):
    stream = StreamIsattyTrue()
    with monkeypatch.context() as context:
        context.setattr(sys, patched, stream, raising=False)
        patch_colorama.win32.winapi_test.return_value = True
        patch_colorama.winterm.enable_vt_processing.return_value = False
        logger.add(stream, colorize=True)
        assert patch_colorama.AnsiToWin32.called


@pytest.mark.parametrize("patched", ["__stdout__", "__stderr__"])
@pytest.mark.skipif(os.name != "nt", reason="Only Windows requires Colorama")
def test_stream_not_wrapped_on_windows_if_vt_support(patched, monkeypatch, patch_colorama):
    stream = StreamIsattyTrue()
    with monkeypatch.context() as context:
        context.setattr(sys, patched, stream, raising=False)
        patch_colorama.win32.winapi_test.return_value = True
        patch_colorama.winterm.enable_vt_processing.return_value = True
        logger.add(stream, colorize=True)
        assert not patch_colorama.AnsiToWin32.called


def test_stream_is_none():
    assert not should_colorize(None)


def test_is_a_tty():
    assert should_colorize(StreamIsattyTrue())


def test_is_not_a_tty():
    assert not should_colorize(StreamIsattyFalse())


def test_is_a_tty_exception():
    assert not should_colorize(StreamIsattyException())


@pytest.mark.parametrize(
    ("patched", "expected"),
    [
        ("__stdout__", True),
        ("__stderr__", True),
        ("stdout", False),
        ("stderr", False),
        ("", False),
    ],
)
def test_pycharm_fixed(monkeypatch, patched, expected):
    stream = StreamIsattyFalse()
    with monkeypatch.context() as context:
        context.setattr(sys, patched, stream, raising=False)
        context.setitem(os.environ, "PYCHARM_HOSTED", "1")
        assert should_colorize(stream) is expected


@pytest.mark.parametrize(
    ("patched", "expected"),
    [
        ("__stdout__", True),
        ("__stderr__", True),
        ("stdout", False),
        ("stderr", False),
        ("", False),
    ],
)
def test_github_actions_fixed(monkeypatch, patched, expected):
    stream = StreamIsattyFalse()
    with monkeypatch.context() as context:
        context.setitem(os.environ, "CI", "1")
        context.setitem(os.environ, "GITHUB_ACTIONS", "1")
        context.setattr(sys, patched, stream, raising=False)
        assert should_colorize(stream) is expected


@pytest.mark.parametrize(
    ("patched", "expected"),
    [
        ("__stdout__", True),
        ("__stderr__", True),
        ("stdout", False),
        ("stderr", False),
        ("", False),
    ],
)
@pytest.mark.skipif(os.name != "nt", reason="The fix is applied only on Windows")
def test_mintty_fixed_windows(monkeypatch, patched, expected):
    stream = StreamIsattyFalse()
    with monkeypatch.context() as context:
        context.setitem(os.environ, "TERM", "xterm")
        context.setattr(sys, patched, stream, raising=False)
        assert should_colorize(stream) is expected


@pytest.mark.parametrize(
    ("patched", "expected"),
    [
        ("__stdout__", False),
        ("__stderr__", False),
        ("stdout", False),
        ("stderr", False),
        ("", False),
    ],
)
@pytest.mark.skipif(os.name == "nt", reason="The fix will be applied on Windows")
def test_mintty_not_fixed_linux(monkeypatch, patched, expected):
    stream = StreamIsattyFalse()
    with monkeypatch.context() as context:
        context.setitem(os.environ, "TERM", "xterm")
        context.setattr(sys, patched, stream, raising=False)
        assert should_colorize(stream) is expected


@pytest.mark.parametrize(
    ("patched", "out_class", "expected"),
    [
        ("stdout", StreamIsattyFalse, True),
        ("stderr", StreamIsattyFalse, True),
        ("__stdout__", StreamIsattyFalse, False),
        ("__stderr__", StreamIsattyFalse, False),
        ("stdout", StreamIsattyTrue, False),
        ("stderr", StreamIsattyTrue, False),
        ("", StreamIsattyFalse, False),
    ],
)
def test_jupyter_fixed(monkeypatch, patched, out_class, expected):
    stream = StreamIsattyFalse()

    class Shell:
        pass

    ipython = MagicMock()
    ipykernel = MagicMock()
    instance = MagicMock()
    instance.__class__ = Shell
    ipython.get_ipython.return_value = instance
    ipykernel.zmqshell.ZMQInteractiveShell = Shell
    ipykernel.iostream.OutStream = out_class

    with monkeypatch.context() as context:
        context.setattr(sys, patched, stream, raising=False)
        context.setattr(builtins, "__IPYTHON__", True, raising=False)
        context.setitem(sys.modules, "IPython", ipython)
        context.setitem(sys.modules, "ipykernel", ipykernel)
        assert should_colorize(stream) is expected


def test_jupyter_missing_lib(monkeypatch):
    # Missing ipykernal so jupyter block will err, should handle gracefully
    stream = StreamIsattyFalse()
    with monkeypatch.context() as context:
        context.setattr(sys, "stdout", stream, raising=False)
        context.setattr(builtins, "__IPYTHON__", True, raising=False)
        assert should_colorize(stream) is False


@pytest.mark.parametrize("patched", ["__stdout__", "__stderr__"])
@pytest.mark.skipif(os.name == "nt", reason="Colorama is required on Windows")
def test_dont_wrap_on_linux(monkeypatch, patched, patch_colorama):
    stream = StreamIsattyTrue()
    with monkeypatch.context() as context:
        context.setattr(sys, patched, stream, raising=False)
        assert not should_wrap(stream)
        assert not patch_colorama.win32.winapi_test.called


@pytest.mark.parametrize("patched", ["stdout", "stderr", ""])
@pytest.mark.skipif(os.name != "nt", reason="Only Windows requires Colorama")
def test_dont_wrap_if_not_original_stdout_or_stderr(monkeypatch, patched, patch_colorama):
    stream = StreamIsattyTrue()
    with monkeypatch.context() as context:
        context.setattr(sys, patched, stream, raising=False)
        assert not should_wrap(stream)
        assert not patch_colorama.win32.winapi_test.called


@pytest.mark.parametrize("patched", ["__stdout__", "__stderr__"])
@pytest.mark.skipif(os.name != "nt", reason="Only Windows requires Colorama")
def test_dont_wrap_if_terminal_has_vt_support(monkeypatch, patched, patch_colorama):
    stream = StreamIsattyTrue()
    with monkeypatch.context() as context:
        context.setattr(sys, patched, stream, raising=False)
        patch_colorama.win32.winapi_test.return_value = True
        patch_colorama.winterm.enable_vt_processing.return_value = True
        assert not should_wrap(stream)
        assert patch_colorama.winterm.enable_vt_processing.called


@pytest.mark.parametrize("patched", ["__stdout__", "__stderr__"])
@pytest.mark.skipif(os.name != "nt", reason="Only Windows requires Colorama")
def test_dont_wrap_if_winapi_false(monkeypatch, patched, patch_colorama):
    stream = StreamIsattyTrue()
    with monkeypatch.context() as context:
        context.setattr(sys, patched, stream, raising=False)
        patch_colorama.win32.winapi_test.return_value = False
        patch_colorama.winterm.enable_vt_processing.return_value = False
        assert not should_wrap(stream)
        assert patch_colorama.win32.winapi_test.called


@pytest.mark.parametrize("patched", ["__stdout__", "__stderr__"])
@pytest.mark.skipif(os.name != "nt", reason="Only Windows requires Colorama")
def test_wrap_if_winapi_true_and_no_vt_support(monkeypatch, patched, patch_colorama):
    stream = StreamIsattyTrue()
    with monkeypatch.context() as context:
        context.setattr(sys, patched, stream, raising=False)
        patch_colorama.win32.winapi_test.return_value = True
        patch_colorama.winterm.enable_vt_processing.return_value = False
        assert should_wrap(stream)
        assert patch_colorama.winterm.enable_vt_processing.called
        assert patch_colorama.win32.winapi_test.called


@pytest.mark.parametrize("patched", ["__stdout__", "__stderr__"])
@pytest.mark.skipif(os.name != "nt", reason="Only Windows requires Colorama")
def test_wrap_if_winapi_true_and_vt_check_fails(monkeypatch, patched, patch_colorama):
    stream = StreamIsattyTrue()
    with monkeypatch.context() as context:
        context.setattr(sys, patched, stream, raising=False)
        patch_colorama.win32.winapi_test.return_value = True
        patch_colorama.winterm.enable_vt_processing.side_effect = RuntimeError
        assert should_wrap(stream)
        assert patch_colorama.winterm.enable_vt_processing.called
        assert patch_colorama.win32.winapi_test.called


@pytest.mark.parametrize("patched", ["__stdout__", "__stderr__"])
@pytest.mark.skipif(os.name != "nt", reason="Only Windows requires Colorama")
def test_wrap_if_winapi_true_and_stream_has_no_fileno(monkeypatch, patched, patch_colorama):
    stream = StreamFilenoException()
    with monkeypatch.context() as context:
        context.setattr(sys, patched, stream, raising=False)
        patch_colorama.win32.winapi_test.return_value = True
        assert should_wrap(stream)
        assert not patch_colorama.winterm.enable_vt_processing.called
        assert patch_colorama.win32.winapi_test.called


@pytest.mark.parametrize("patched", ["__stdout__", "__stderr__"])
@pytest.mark.skipif(os.name != "nt", reason="Only Windows requires Colorama")
def test_wrap_if_winapi_true_and_old_colorama_version(monkeypatch, patched, patch_colorama):
    stream = StreamIsattyTrue()
    with monkeypatch.context() as context:
        context.setattr(sys, patched, stream, raising=False)
        patch_colorama.win32.winapi_test.return_value = True
        del patch_colorama.winterm.enable_vt_processing
        assert should_wrap(stream)
        assert patch_colorama.win32.winapi_test.called
