#!/usr/bin/python3
"""Tests for slpkg/config.py using pytest."""

from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest
from slpkg.config import Config


@pytest.fixture
def mock_config():
    """Fixture to create a Config instance with critical methods patched out."""
    with patch("slpkg.config.Config._load_config"), \
         patch("slpkg.config.Config._create_paths"), \
         patch("slpkg.config.Config._set_colors"):
        config = Config()
        # Set some defaults manually if needed by tests
        config.colors = True
        return config


def test_config_initialization_defaults(mock_config):
    """Test that default values are correctly set during initialization."""
    assert mock_config.prog_name == "slpkg"
    assert mock_config.tmp_path == Path("/tmp")
    assert mock_config.answer_yes is False
    assert mock_config.logging_level == "INFO"
    assert mock_config.maximum_parallel == 5
    assert mock_config.downloader == "wget"
    assert mock_config.verify_skip_virtual == ["dev/", "proc/", "sys/", "run/"]
    assert mock_config.verify_skip_prefixes == ["install/"]
    assert mock_config.verify_skip_suffixes == [".new", ".sample"]
    assert mock_config.all_repos_confirm == "once"
    assert mock_config.mirror_version_check is True


def test_is_64bit(mock_config):
    """Test the is_64bit method with various architectures."""
    # 64-bit case
    mock_config.cpu_arch = "x86_64"
    mock_config.os_arch = "64bit"
    assert mock_config.is_64bit() is True

    mock_config.cpu_arch = "aarch64"
    mock_config.os_arch = "64bit"
    assert mock_config.is_64bit() is True

    # 32-bit cases
    mock_config.cpu_arch = "i686"
    mock_config.os_arch = "32bit"
    assert mock_config.is_64bit() is False

    mock_config.cpu_arch = "x86_64"
    mock_config.os_arch = "32bit"
    assert mock_config.is_64bit() is False


def test_update_from_args(mock_config):
    """Test updating configuration from argparse arguments."""
    args = MagicMock()
    args.option_color = "off"
    args.option_dialog = True
    args.option_parallel = True
    args.option_quiet = True
    args.option_yes = True

    with patch("slpkg.config.DIALOG_AVAILABLE", True), \
         patch.object(mock_config, "_set_colors") as mock_set_colors:
        mock_config.update_from_args(args)
        assert mock_config.colors is False
        assert mock_config.dialog is True
        assert mock_config.parallel_downloads is True
        assert mock_config.quiet is True
        assert mock_config.answer_yes is True
        mock_set_colors.assert_called_once()

    # Test color 'on'
    args.option_color = "on"
    mock_config.update_from_args(args)
    assert mock_config.colors is True


def test_set_colors(mock_config):
    """Test terminal color code assignment."""
    mock_config.colors = True
    mock_config._set_colors()
    assert mock_config.red == "\x1b[91m"
    assert mock_config.endc == "\x1b[0m"
    assert mock_config.bold == "\x1b[1m"

    mock_config.colors = False
    mock_config._set_colors()
    assert mock_config.red == ""
    assert mock_config.endc == ""
    assert mock_config.bold == ""


def test_load_config_success(mock_config):
    """Test successful configuration loading from a TOML file."""
    mock_toml_data = {
        "GLOBAL": {
            "LOGGING_LEVEL": "DEBUG",
            "COLORS": False,
            "MAXIMUM_PARALLEL": 10,
            "DOWNLOADER": "curl",
            "VERIFY_SKIP_VIRTUAL": ["dev/", "proc/"],
            "VERIFY_SKIP_PREFIXES": ["install/", "var/lib/clamav/"],
            "VERIFY_SKIP_SUFFIXES": [".new", ".sample", ".db"],
        }
    }

    with patch("slpkg.config.Path.exists", return_value=True), \
         patch("slpkg.config.Path.read_text", return_value="dummy content"), \
         patch("tomlkit.parse", return_value=mock_toml_data):
        mock_config._load_config()
        assert mock_config.logging_level == "DEBUG"
        assert mock_config.colors is False
        assert mock_config.maximum_parallel == 10
        assert mock_config.downloader == "curl"
        assert mock_config.verify_skip_virtual == ["dev/", "proc/"]
        assert mock_config.verify_skip_prefixes == ["install/", "var/lib/clamav/"]
        assert mock_config.verify_skip_suffixes == [".new", ".sample", ".db"]


def test_load_config_type_mismatch(mock_config, capsys):
    """Test configuration loading with a type mismatch in TOML."""
    mock_toml_data = {
        "GLOBAL": {
            "MAXIMUM_PARALLEL": "not-an-int"
        }
    }

    with patch("slpkg.config.Path.exists", return_value=True), \
         patch("slpkg.config.Path.read_text", return_value="dummy content"), \
         patch("tomlkit.parse", return_value=mock_toml_data):
        # We need to make sure we have the default value before loading
        mock_config.maximum_parallel = 5
        mock_config._load_config()
        captured = capsys.readouterr()
        assert "Error: Setting 'MAXIMUM_PARALLEL' in configurations contain wrong type." in captured.out
        # Value should remain default because of break in _load_config
        assert mock_config.maximum_parallel == 5


def test_load_config_missing_keys(mock_config, capsys):
    """Test configuration loading when some keys are missing from TOML."""
    # Empty TOML
    mock_toml_data = {"SECTION": {}}

    with patch("slpkg.config.Path.exists", return_value=True), \
         patch("slpkg.config.Path.read_text", return_value="dummy content"), \
         patch("tomlkit.parse", return_value=mock_toml_data):
        mock_config._load_config()
        captured = capsys.readouterr()
        assert "Important notification: Unable to find" in captured.out
        assert "LOGGING_LEVEL" in captured.out


# ---------------------------------------------------------------------------
# _read_toml_file
# ---------------------------------------------------------------------------


def test_read_toml_file_parses_existing_file(mock_config, tmp_path) -> None:
    """Existing TOML file is parsed and returned as a dict."""
    toml_file = tmp_path / "slpkg.toml"
    toml_file.write_text("[SLPKG]\nCOLORS = true\n", encoding="utf-8")
    result = mock_config._read_toml_file(toml_file)
    assert "SLPKG" in result
    assert result["SLPKG"]["COLORS"] is True


def test_read_toml_file_missing_returns_empty(mock_config, tmp_path) -> None:
    """Missing TOML file returns an empty dict (no exception)."""
    result = mock_config._read_toml_file(tmp_path / "nonexistent.toml")
    assert result == {}


# ---------------------------------------------------------------------------
# _flatten_toml
# ---------------------------------------------------------------------------


def test_flatten_toml_merges_sections(mock_config) -> None:
    """Keys from all sections are merged into one flat dict."""
    conf = {
        "SEC_A": {"KEY1": "val1", "KEY2": 42},
        "SEC_B": {"KEY3": True},
    }
    flat = Config._flatten_toml(conf)
    assert flat == {"KEY1": "val1", "KEY2": 42, "KEY3": True}


def test_flatten_toml_skips_non_dict_sections(mock_config) -> None:
    """Top-level non-dict values (e.g. comments stored as strings) are ignored."""
    conf = {"META": "not-a-dict", "SLPKG": {"COLORS": True}}
    flat = Config._flatten_toml(conf)
    assert "COLORS" in flat
    assert "META" not in flat


def test_flatten_toml_empty_conf_returns_empty(mock_config) -> None:
    assert Config._flatten_toml({}) == {}


# ---------------------------------------------------------------------------
# _apply_toml_values
# ---------------------------------------------------------------------------


def test_apply_toml_values_sets_attribute(mock_config) -> None:
    """Valid key+value sets the corresponding instance attribute."""
    mock_config.config = {"DOWNLOADER": "curl"}
    mock_config._apply_toml_values()
    assert mock_config.downloader == "curl"


def test_apply_toml_values_type_error_prints_message(mock_config, capsys) -> None:
    """Type mismatch prints an error message and leaves attribute at default."""
    mock_config.maximum_parallel = 5
    mock_config.config = {"MAXIMUM_PARALLEL": "bad"}
    mock_config._apply_toml_values()
    assert "wrong type" in capsys.readouterr().out
    assert mock_config.maximum_parallel == 5


def test_apply_toml_values_missing_key_prints_notification(mock_config, capsys) -> None:
    """Missing key prints the 'Important notification' message."""
    mock_config.config = {}
    mock_config._apply_toml_values()
    assert "Important notification" in capsys.readouterr().out


def test_apply_toml_values_download_only_path_converted(mock_config) -> None:
    """DOWNLOAD_ONLY_PATH string is converted to a Path instance."""
    mock_config.config = {"DOWNLOAD_ONLY_PATH": "/tmp/slpkg"}
    mock_config._apply_toml_values()
    assert isinstance(mock_config.download_only_path, Path)
    assert mock_config.download_only_path == Path("/tmp/slpkg")


# ---------------------------------------------------------------------------
# _check_dialog_available
# ---------------------------------------------------------------------------


def test_check_dialog_available_disables_when_missing(mock_config) -> None:
    """self.dialog is forced to False when python-dialog is not installed."""
    mock_config.dialog = True
    with patch("slpkg.config.DIALOG_AVAILABLE", False):
        mock_config._check_dialog_available()
    assert mock_config.dialog is False


def test_check_dialog_available_keeps_true_when_present(mock_config) -> None:
    """self.dialog stays True when python-dialog is available."""
    mock_config.dialog = True
    with patch("slpkg.config.DIALOG_AVAILABLE", True):
        mock_config._check_dialog_available()
    assert mock_config.dialog is True


# ---------------------------------------------------------------------------
# class-level constants sanity checks
# ---------------------------------------------------------------------------


def test_toml_config_entries_are_well_formed() -> None:
    """Every _TOML_CONFIG entry must be a (str, tuple[type, ...]) pair."""
    for key, (attr_name, expected_types) in Config._TOML_CONFIG.items():
        assert isinstance(attr_name, str), f"{key}: attr_name must be str"
        assert isinstance(expected_types, tuple) and expected_types, f"{key}: expected_types must be a non-empty tuple"
        assert all(isinstance(t, type) for t in expected_types), f"{key}: all elements must be types"


def test_create_paths_root(mock_config):
    """Test path creation logic when running as root."""
    with patch("pathlib.Path.mkdir") as mock_mkdir, \
         patch("pathlib.Path.is_dir", return_value=False):
        mock_config._create_paths()
        assert mock_mkdir.call_count >= 5
