#!/usr/bin/python3
"""Tests for slpkg/cli_parser.py — argument parser, formatters, and helpers."""

import argparse
from io import StringIO
from unittest.mock import patch

import pytest

from slpkg.cli_parser import (
    CustomCommandsFormatter,
    SlpkgParser,
    SubCommandFormatter,
    _COMMAND_CATEGORIES,
    _EPILOG_BASE,
    _EPILOG_MAIN,
    _collect_subparser_info,
    _print_category_help,
    build_parser,
)


# ── _COMMAND_CATEGORIES ───────────────────────────────────────────────────────


class TestCommandCategories:
    def test_is_list_of_tuples(self):
        assert isinstance(_COMMAND_CATEGORIES, list)
        for item in _COMMAND_CATEGORIES:
            assert isinstance(item, tuple)
            assert len(item) == 2

    def test_category_names_are_strings(self):
        for cat_name, _ in _COMMAND_CATEGORIES:
            assert isinstance(cat_name, str) and cat_name

    def test_each_category_has_commands(self):
        for _, cmds in _COMMAND_CATEGORIES:
            assert isinstance(cmds, list) and len(cmds) > 0

    def test_expected_categories_present(self):
        names = {cat for cat, _ in _COMMAND_CATEGORIES}
        assert {"Repository", "Packages", "Query", "Dependencies", "System"} <= names

    def test_key_commands_are_categorised(self):
        all_cmds = {cmd for _, cmds in _COMMAND_CATEGORIES for cmd in cmds}
        for expected in ("install", "update", "upgrade", "search", "depends", "help", "version"):
            assert expected in all_cmds


# ── _EPILOG strings ───────────────────────────────────────────────────────────


class TestEpilogs:
    def test_base_epilog_contains_help_hint(self):
        assert "slpkg help <COMMAND>" in _EPILOG_BASE

    def test_main_epilog_extends_base(self):
        assert _EPILOG_BASE in _EPILOG_MAIN

    def test_main_epilog_lists_categories(self):
        for cat_name, _ in _COMMAND_CATEGORIES:
            assert cat_name.lower() in _EPILOG_MAIN


# ── SlpkgParser.error ─────────────────────────────────────────────────────────


class TestSlpkgParserError:
    def test_invalid_choice_shows_unknown_command_message(self, capsys):
        p = SlpkgParser(prog="slpkg")
        with pytest.raises(SystemExit) as exc:
            p.error("argument command: invalid choice: 'foo' (choose from ...)")
        assert exc.value.code == 2
        assert "Unknown command 'foo'" in capsys.readouterr().err

    def test_other_error_delegates_to_argparse(self, capsys):
        p = SlpkgParser(prog="slpkg")
        with pytest.raises(SystemExit) as exc:
            p.error("something went wrong")
        assert exc.value.code == 2
        assert "something went wrong" in capsys.readouterr().err


# ── build_parser ──────────────────────────────────────────────────────────────


class TestBuildParser:
    @pytest.fixture(autouse=True)
    def _mock_cfg(self):
        with patch("slpkg.cli_parser.config_load") as mock:
            mock.bold = ""
            mock.endc = ""
            yield mock

    def test_returns_tuple_of_parser_and_dict(self):
        parser, subparsers_map = build_parser()
        assert isinstance(parser, argparse.ArgumentParser)
        assert isinstance(subparsers_map, dict)

    def test_subparsers_map_contains_primary_commands(self):
        _, subparsers_map = build_parser()
        for cmd in ("install", "update", "upgrade", "search", "remove", "help", "version", "rebuild", "reinstall"):
            assert cmd in subparsers_map, f"'{cmd}' missing from subparsers_map"

    def test_parse_version_command(self):
        parser, _ = build_parser()
        args = parser.parse_args(["version"])
        assert args.command == "version"

    def test_parse_install_command_with_packages(self):
        parser, _ = build_parser()
        args = parser.parse_args(["install", "pkg1", "pkg2"])
        assert args.command == "install"
        assert args.packages == ["pkg1", "pkg2"]

    def test_parse_update_command(self):
        parser, _ = build_parser()
        args = parser.parse_args(["update"])
        assert args.command == "update"

    def test_parse_search_with_repo_option(self):
        parser, _ = build_parser()
        args = parser.parse_args(["-o", "slack", "search", "pkg"])
        assert args.command == "search"
        assert args.option_repository_global == "slack"

    def test_no_lock_flag_sets_option(self):
        parser, _ = build_parser()
        args = parser.parse_args(["version", "--no-lock"])
        assert args.option_no_lock is True

    def test_yes_flag_on_install(self):
        parser, _ = build_parser()
        args = parser.parse_args(["install", "-y", "pkg1"])
        assert args.option_yes is True

    def test_check_flag_alias_dry_run(self):
        parser, _ = build_parser()
        args = parser.parse_args(["install", "--dry-run", "pkg1"])
        assert args.option_check is True

    def test_help_command_accepts_subcommand_argument(self):
        parser, _ = build_parser()
        args = parser.parse_args(["help", "install"])
        assert args.command == "help"
        assert args.command_for_help == "install"

    def test_history_with_package_option(self):
        parser, _ = build_parser()
        args = parser.parse_args(["history", "--package", "python"])
        assert args.option_history_package == "python"

    def test_json_flag_on_list(self):
        parser, _ = build_parser()
        args = parser.parse_args(["list", "--json", "python"])
        assert args.option_json is True


# ── _collect_subparser_info ───────────────────────────────────────────────────


class TestCollectSubparserInfo:
    @pytest.fixture(autouse=True)
    def _mock_cfg(self):
        with patch("slpkg.cli_parser.config_load") as mock:
            mock.bold = ""
            mock.endc = ""
            yield mock

    def test_returns_dict(self):
        parser, _ = build_parser()
        info = _collect_subparser_info(parser)
        assert isinstance(info, dict)

    def test_contains_primary_commands(self):
        parser, _ = build_parser()
        info = _collect_subparser_info(parser)
        for cmd in ("install", "update", "upgrade", "search", "remove"):
            assert cmd in info, f"'{cmd}' missing from subparser info"

    def test_values_are_two_tuples_of_strings(self):
        parser, _ = build_parser()
        info = _collect_subparser_info(parser)
        for meta, help_text in info.values():
            assert isinstance(meta, str)
            assert isinstance(help_text, str)

    def test_empty_parser_returns_empty_dict(self):
        p = argparse.ArgumentParser()
        assert _collect_subparser_info(p) == {}


# ── _print_category_help ──────────────────────────────────────────────────────


class TestPrintCategoryHelp:
    @pytest.fixture(autouse=True)
    def _mock_cfg(self):
        with patch("slpkg.cli_parser.config_load") as mock:
            mock.bold = ""
            mock.endc = ""
            yield mock

    def test_prints_category_name(self, capsys):
        parser, _ = build_parser()
        _print_category_help("packages", parser)
        out = capsys.readouterr().out
        assert "Packages" in out

    def test_prints_commands_in_category(self, capsys):
        parser, _ = build_parser()
        _print_category_help("repository", parser)
        out = capsys.readouterr().out
        assert "update" in out

    def test_prints_epilog_base(self, capsys):
        parser, _ = build_parser()
        _print_category_help("system", parser)
        out = capsys.readouterr().out
        assert "slpkg help <COMMAND>" in out

    def test_prints_usage_line(self, capsys):
        parser, _ = build_parser()
        _print_category_help("query", parser)
        out = capsys.readouterr().out
        assert "Usage" in out


# ── CustomCommandsFormatter ───────────────────────────────────────────────────


class TestCustomCommandsFormatter:
    @pytest.fixture(autouse=True)
    def _mock_cfg(self):
        with patch("slpkg.cli_parser.config_load") as mock:
            mock.bold = ""
            mock.endc = ""
            yield mock

    def test_initialises_with_custom_usage(self):
        fmt = CustomCommandsFormatter("slpkg")
        assert "<COMMAND>" in fmt.custom_usage

    def test_format_help_contains_category_headers(self):
        parser, _ = build_parser()
        help_text = parser.format_help()
        assert "Packages:" in help_text or "Repository:" in help_text


# ── SubCommandFormatter ───────────────────────────────────────────────────────


class TestSubCommandFormatter:
    def test_has_wider_help_position(self):
        fmt = SubCommandFormatter("slpkg install")
        # max_help_position is 36 vs the default 24
        assert fmt._max_help_position == 36
