#!/usr/bin/python3
"""Tests for slpkg.progress_bar.ProgressBar."""

from unittest.mock import MagicMock, patch

import pytest

from slpkg.progress_bar import ProgressBar


def make_progress_bar() -> ProgressBar:
    """Construct ProgressBar bypassing real __init__ (avoids config_load/Imprint)."""
    obj: ProgressBar = object.__new__(ProgressBar)
    obj.progress_spinner = "spinner"
    obj.colors = False
    obj.spinner_color = "green"
    obj.endc = ""
    obj.imp = MagicMock()
    obj.color = ""
    obj.spinners: dict = {}
    obj.spinners_color: dict = {}
    obj.spinner: tuple = ()
    obj.bar_message = ""
    return obj


# ---------------------------------------------------------------------------
# assign_spinner_colors
# ---------------------------------------------------------------------------


def test_assign_spinner_colors_sets_all_keys() -> None:
    obj = make_progress_bar()
    with patch("slpkg.progress_bar.config_load") as mock_config:
        for attr in ("green", "yellow", "cyan", "grey", "red", "white"):
            setattr(mock_config, attr, f"_{attr}_")
        obj.assign_spinner_colors()
    assert set(obj.spinners_color.keys()) == {"green", "yellow", "cyan", "grey", "red", "white"}


def test_assign_spinner_colors_values_from_config() -> None:
    obj = make_progress_bar()
    with patch("slpkg.progress_bar.config_load") as mock_config:
        mock_config.green = "GREEN"
        mock_config.yellow = "YELLOW"
        mock_config.cyan = "CYAN"
        mock_config.grey = "GREY"
        mock_config.red = "RED"
        mock_config.white = "WHITE"
        obj.assign_spinner_colors()
    assert obj.spinners_color["green"] == "GREEN"
    assert obj.spinners_color["red"] == "RED"


def test_assign_spinner_colors_six_entries() -> None:
    obj = make_progress_bar()
    with patch("slpkg.progress_bar.config_load"):
        obj.assign_spinner_colors()
    assert len(obj.spinners_color) == 6


# ---------------------------------------------------------------------------
# assign_spinner_chars
# ---------------------------------------------------------------------------


def test_assign_spinner_chars_sets_all_keys() -> None:
    obj = make_progress_bar()
    obj.assign_spinner_chars()
    expected = {"spinner", "pie", "moon", "line", "pixel", "ball", "clock"}
    assert set(obj.spinners.keys()) == expected


def test_assign_spinner_chars_spinner_has_four_frames() -> None:
    obj = make_progress_bar()
    obj.assign_spinner_chars()
    assert len(obj.spinners["spinner"]) == 4


def test_assign_spinner_chars_pie_has_four_frames() -> None:
    obj = make_progress_bar()
    obj.assign_spinner_chars()
    assert len(obj.spinners["pie"]) == 4


def test_assign_spinner_chars_ball_has_four_frames() -> None:
    obj = make_progress_bar()
    obj.assign_spinner_chars()
    assert len(obj.spinners["ball"]) == 4


def test_assign_spinner_chars_pixel_has_eight_frames() -> None:
    obj = make_progress_bar()
    obj.assign_spinner_chars()
    assert len(obj.spinners["pixel"]) == 8


# ---------------------------------------------------------------------------
# set_spinner
# ---------------------------------------------------------------------------


def test_set_spinner_default_spinner() -> None:
    obj = make_progress_bar()
    obj.assign_spinner_chars()
    obj.progress_spinner = "spinner"
    obj.set_spinner()
    assert obj.spinner == ("-", "\\", "|", "/")


def test_set_spinner_pie() -> None:
    obj = make_progress_bar()
    obj.assign_spinner_chars()
    obj.progress_spinner = "pie"
    obj.set_spinner()
    assert obj.spinner == obj.spinners["pie"]


def test_set_spinner_moon() -> None:
    obj = make_progress_bar()
    obj.assign_spinner_chars()
    obj.progress_spinner = "moon"
    obj.set_spinner()
    assert obj.spinner == obj.spinners["moon"]


def test_set_spinner_fallback_on_invalid_key() -> None:
    obj = make_progress_bar()
    obj.assign_spinner_chars()
    obj.progress_spinner = "nonexistent_xyz"
    obj.set_spinner()
    assert obj.spinner == obj.spinners["spinner"]


def test_set_spinner_fallback_is_four_frames() -> None:
    obj = make_progress_bar()
    obj.assign_spinner_chars()
    obj.progress_spinner = "invalid"
    obj.set_spinner()
    assert len(obj.spinner) == 4


# ---------------------------------------------------------------------------
# set_color
# ---------------------------------------------------------------------------


def test_set_color_valid_green() -> None:
    obj = make_progress_bar()
    obj.spinners_color = {"green": "GREEN", "red": "RED"}
    obj.spinner_color = "green"
    obj.set_color()
    assert obj.color == "GREEN"


def test_set_color_valid_red() -> None:
    obj = make_progress_bar()
    obj.spinners_color = {"green": "GREEN", "red": "RED"}
    obj.spinner_color = "red"
    obj.set_color()
    assert obj.color == "RED"


def test_set_color_fallback_on_invalid_key() -> None:
    obj = make_progress_bar()
    obj.spinners_color = {"green": "GREEN"}
    obj.spinner_color = "magenta"
    obj.set_color()
    assert obj.color == ""


def test_set_color_empty_spinners_color_falls_back() -> None:
    obj = make_progress_bar()
    obj.spinners_color = {}
    obj.spinner_color = "green"
    obj.set_color()
    assert obj.color == ""


# ---------------------------------------------------------------------------
# set_the_spinner_message
# ---------------------------------------------------------------------------


def test_set_spinner_message_no_filename() -> None:
    obj = make_progress_bar()
    obj.set_the_spinner_message("", "Downloading")
    assert obj.bar_message == "Downloading... "


def test_set_spinner_message_no_filename_building() -> None:
    obj = make_progress_bar()
    obj.set_the_spinner_message("", "Building")
    assert obj.bar_message == "Building... "


def test_set_spinner_message_with_short_filename_contains_name() -> None:
    obj = make_progress_bar()
    obj.set_the_spinner_message("myfile.txz", "Installing", prefix="  ")
    assert "myfile.txz" in obj.bar_message


def test_set_spinner_message_with_short_filename_contains_action() -> None:
    obj = make_progress_bar()
    obj.set_the_spinner_message("myfile.txz", "Installing", prefix="  ")
    assert "Installing" in obj.bar_message


def test_set_spinner_message_long_filename_truncated() -> None:
    obj = make_progress_bar()
    long_name = "a" * 500
    obj.set_the_spinner_message(long_name, "Installing", prefix="  ")
    assert "..." in obj.bar_message


def test_set_spinner_message_short_filename_not_truncated() -> None:
    obj = make_progress_bar()
    obj.set_the_spinner_message("short.txz", "Building", prefix="  ")
    assert "..." not in obj.bar_message


def test_set_spinner_message_no_filename_ignores_prefix() -> None:
    # With no filename the prefix is not used in the message
    obj = make_progress_bar()
    obj.set_the_spinner_message("", "Upgrading", prefix="( 1/10) ")
    assert obj.bar_message == "Upgrading... "


# ---------------------------------------------------------------------------
# progress_bar() — KeyboardInterrupt exits with code 1
# ---------------------------------------------------------------------------


def test_progress_bar_keyboard_interrupt_raises_systemexit() -> None:
    obj = make_progress_bar()
    obj.assign_spinner_chars()
    with patch("slpkg.progress_bar.config_load"):
        obj.assign_spinner_colors()
    obj.set_spinner()
    obj.set_color()
    obj.bar_message = "Downloading... "

    with patch("slpkg.progress_bar.time.sleep", side_effect=KeyboardInterrupt):
        with pytest.raises(SystemExit) as exc_info:
            obj.progress_bar("Downloading")
    assert exc_info.value.code == 1


def test_progress_bar_orchestrates_setup_methods() -> None:
    """progress_bar() calls assign_spinner_chars, assign_spinner_colors, set_spinner,
    set_color and set_the_spinner_message before starting the spinner loop."""
    obj = make_progress_bar()
    call_log: list[str] = []

    def _wrap(name: str, fn):
        def wrapper(*a, **kw):
            call_log.append(name)
            return fn(*a, **kw)
        return wrapper

    obj.assign_spinner_chars = _wrap("chars", lambda: obj.__class__.assign_spinner_chars(obj))
    obj.assign_spinner_colors = _wrap("colors", lambda: None)
    obj.set_spinner = _wrap("spinner", lambda: setattr(obj, "spinner", ("-",)))
    obj.set_color = _wrap("color", lambda: None)
    obj.set_the_spinner_message = _wrap("msg", lambda f, m, prefix="  ": setattr(obj, "bar_message", f"{m}... "))

    with patch("slpkg.progress_bar.time.sleep", side_effect=KeyboardInterrupt):
        with pytest.raises(SystemExit):
            obj.progress_bar("Downloading")

    for expected in ("chars", "colors", "spinner", "color", "msg"):
        assert expected in call_log, f"Expected '{expected}' to be called, got {call_log}"
