import io
import signal
import sys
import time
from datetime import timedelta

import progressbar
from progressbar import terminal


def test_left_justify() -> None:
    """Left justify using the terminal width"""
    p = progressbar.ProgressBar(
        widgets=[progressbar.BouncingBar(marker=progressbar.RotatingMarker())],
        max_value=100,
        term_width=20,
        left_justify=True,
    )

    assert p.term_width is not None
    for i in range(100):
        p.update(i)


def test_right_justify() -> None:
    """Right justify using the terminal width"""
    p = progressbar.ProgressBar(
        widgets=[progressbar.BouncingBar(marker=progressbar.RotatingMarker())],
        max_value=100,
        term_width=20,
        left_justify=False,
    )

    assert p.term_width is not None
    for i in range(100):
        p.update(i)


def test_auto_width(monkeypatch) -> None:
    """Right justify using the terminal width"""

    def ioctl(*args):
        return '\xbf\x00\xeb\x00\x00\x00\x00\x00'

    def fake_signal(signal, func):
        pass

    try:
        import fcntl

        monkeypatch.setattr(fcntl, 'ioctl', ioctl)
        monkeypatch.setattr(signal, 'signal', fake_signal)
        p = progressbar.ProgressBar(
            widgets=[
                progressbar.BouncingBar(marker=progressbar.RotatingMarker()),
            ],
            max_value=100,
            left_justify=True,
            term_width=None,
        )

        assert p.term_width is not None
        for i in range(100):
            p.update(i)
    except ImportError:
        pass  # Skip on Windows


def test_fill_right() -> None:
    """Right justify using the terminal width"""
    p = progressbar.ProgressBar(
        widgets=[progressbar.BouncingBar(fill_left=False)],
        max_value=100,
        term_width=20,
    )

    assert p.term_width is not None
    for i in range(100):
        p.update(i)


def test_fill_left() -> None:
    """Right justify using the terminal width"""
    p = progressbar.ProgressBar(
        widgets=[progressbar.BouncingBar(fill_left=True)],
        max_value=100,
        term_width=20,
    )

    assert p.term_width is not None
    for i in range(100):
        p.update(i)


def test_no_fill(monkeypatch) -> None:
    """Simply bounce within the terminal width"""
    bar = progressbar.BouncingBar()
    bar.INTERVAL = timedelta(seconds=1)
    p = progressbar.ProgressBar(
        widgets=[bar],
        max_value=progressbar.UnknownLength,
        term_width=20,
    )

    assert p.term_width is not None
    for i in range(30):
        p.update(i, force=True)
        # Fake the start time so we can actually emulate a moving progress bar
        p.start_time = p.start_time - timedelta(seconds=i)


def test_stdout_redirection() -> None:
    p = progressbar.ProgressBar(
        fd=sys.stdout,
        max_value=10,
        redirect_stdout=True,
    )

    for i in range(10):
        print('', file=sys.stdout)
        p.update(i)


def test_double_stdout_redirection() -> None:
    p = progressbar.ProgressBar(max_value=10, redirect_stdout=True)
    p2 = progressbar.ProgressBar(max_value=10, redirect_stdout=True)

    for i in range(10):
        print('', file=sys.stdout)
        p.update(i)
        p2.update(i)


def test_stderr_redirection() -> None:
    p = progressbar.ProgressBar(max_value=10, redirect_stderr=True)

    for i in range(10):
        print('', file=sys.stderr)
        p.update(i)


def test_stdout_stderr_redirection() -> None:
    p = progressbar.ProgressBar(
        max_value=10,
        redirect_stdout=True,
        redirect_stderr=True,
    )
    p.start()

    for i in range(10):
        time.sleep(0.01)
        print('', file=sys.stdout)
        print('', file=sys.stderr)
        p.update(i)

    p.finish()


def test_resize(monkeypatch) -> None:
    def ioctl(*args):
        return '\xbf\x00\xeb\x00\x00\x00\x00\x00'

    def fake_signal(signal, func):
        pass

    try:
        import fcntl

        monkeypatch.setattr(fcntl, 'ioctl', ioctl)
        monkeypatch.setattr(signal, 'signal', fake_signal)

        p = progressbar.ProgressBar(max_value=10)
        p.start()

        for i in range(10):
            p.update(i)
            p._handle_resize()

        p.finish()
    except ImportError:
        pass  # Skip on Windows


def test_base() -> None:
    assert str(terminal.CUP)
    assert str(terminal.CLEAR_SCREEN_ALL_AND_HISTORY)

    terminal.clear_line(0)
    terminal.clear_line(1)


def _redirect_update_output(*, redirect_blank_line: bool) -> str:
    # Return only what a single update() writes while redirected output is
    # pending a clear (the bar otherwise uses '\r', never '\n').
    fd = io.StringIO()
    bar = progressbar.ProgressBar(
        max_value=10,
        fd=fd,
        redirect_blank_line=redirect_blank_line,
        is_terminal=True,
        term_width=40,
    )
    bar.start()
    fd.seek(0)
    fd.truncate(0)  # drop the start frame
    bar.update(5, force=True)
    return fd.getvalue()


def test_redirect_blank_line_separator(monkeypatch) -> None:
    # #295: opt-in blank line between redirected output and the bar. Force
    # `needs_clear` so the test does not depend on global stream state.
    from progressbar import utils

    monkeypatch.setattr(utils.streams, 'needs_clear', lambda: True)
    assert '\n' in _redirect_update_output(redirect_blank_line=True)


def test_redirect_blank_line_off_by_default(monkeypatch) -> None:
    # Default behaviour is unchanged: no separator even with output pending.
    from progressbar import utils

    monkeypatch.setattr(utils.streams, 'needs_clear', lambda: True)
    assert '\n' not in _redirect_update_output(redirect_blank_line=False)


def test_redirect_blank_line_position(monkeypatch) -> None:
    # #295: the blank line must sit immediately AFTER the clear sequence and
    # BEFORE the bar redraw, not merely 'somewhere in the output'.
    from progressbar import utils

    monkeypatch.setattr(utils.streams, 'needs_clear', lambda: True)
    output = _redirect_update_output(redirect_blank_line=True)
    clear = '\r' + ' ' * 40 + '\r'
    assert (clear + '\n') in output, repr(output)
    assert output.index(clear + '\n') < output.index('50%'), repr(output)

    output_off = _redirect_update_output(redirect_blank_line=False)
    assert (clear + '\n') not in output_off, repr(output_off)