"""
Tests for ribbon_builder module
"""

import pytest
from io import StringIO
from unittest.mock import Mock, patch

from pyPhotoAlbum.ribbon_builder import (
    build_ribbon_config,
    get_keyboard_shortcuts,
    validate_ribbon_config,
    print_ribbon_summary,
)


class TestBuildRibbonConfig:
    """Tests for build_ribbon_config function"""

    def test_empty_class(self):
        """Test with a class that has no ribbon actions"""

        class EmptyClass:
            pass

        config = build_ribbon_config(EmptyClass)
        assert config == {}

    def test_single_action(self):
        """Test with a class that has one ribbon action"""

        class SingleAction:
            def my_action(self):
                pass

            my_action._ribbon_action = {
                "tab": "Home",
                "group": "File",
                "label": "My Action",
                "action": "my_action",
                "tooltip": "Does something",
            }

        config = build_ribbon_config(SingleAction)

        assert "Home" in config
        assert len(config["Home"]["groups"]) == 1
        assert config["Home"]["groups"][0]["name"] == "File"
        assert len(config["Home"]["groups"][0]["actions"]) == 1
        assert config["Home"]["groups"][0]["actions"][0]["label"] == "My Action"

    def test_multiple_actions_same_group(self):
        """Test with multiple actions in the same group"""

        class MultiAction:
            def action1(self):
                pass

            action1._ribbon_action = {
                "tab": "Home",
                "group": "Edit",
                "label": "Action 1",
                "action": "action1",
                "tooltip": "First action",
            }

            def action2(self):
                pass

            action2._ribbon_action = {
                "tab": "Home",
                "group": "Edit",
                "label": "Action 2",
                "action": "action2",
                "tooltip": "Second action",
            }

        config = build_ribbon_config(MultiAction)

        assert "Home" in config
        assert len(config["Home"]["groups"]) == 1
        assert config["Home"]["groups"][0]["name"] == "Edit"
        assert len(config["Home"]["groups"][0]["actions"]) == 2

    def test_multiple_groups(self):
        """Test with actions in different groups"""

        class MultiGroup:
            def action1(self):
                pass

            action1._ribbon_action = {
                "tab": "Home",
                "group": "File",
                "label": "File Action",
                "action": "action1",
                "tooltip": "File stuff",
            }

            def action2(self):
                pass

            action2._ribbon_action = {
                "tab": "Home",
                "group": "Edit",
                "label": "Edit Action",
                "action": "action2",
                "tooltip": "Edit stuff",
            }

        config = build_ribbon_config(MultiGroup)

        assert "Home" in config
        assert len(config["Home"]["groups"]) == 2
        group_names = [g["name"] for g in config["Home"]["groups"]]
        assert "File" in group_names
        assert "Edit" in group_names

    def test_multiple_tabs(self):
        """Test with actions in different tabs"""

        class MultiTab:
            def action1(self):
                pass

            action1._ribbon_action = {
                "tab": "Home",
                "group": "File",
                "label": "Home Action",
                "action": "action1",
                "tooltip": "Home stuff",
            }

            def action2(self):
                pass

            action2._ribbon_action = {
                "tab": "View",
                "group": "Zoom",
                "label": "View Action",
                "action": "action2",
                "tooltip": "View stuff",
            }

        config = build_ribbon_config(MultiTab)

        assert "Home" in config
        assert "View" in config

    def test_tab_ordering(self):
        """Test that tabs are ordered correctly"""

        class OrderedTabs:
            def action1(self):
                pass

            action1._ribbon_action = {
                "tab": "Export",
                "group": "Export",
                "label": "Export",
                "action": "action1",
                "tooltip": "Export",
            }

            def action2(self):
                pass

            action2._ribbon_action = {
                "tab": "Home",
                "group": "File",
                "label": "Home",
                "action": "action2",
                "tooltip": "Home",
            }

            def action3(self):
                pass

            action3._ribbon_action = {
                "tab": "View",
                "group": "Zoom",
                "label": "View",
                "action": "action3",
                "tooltip": "View",
            }

        config = build_ribbon_config(OrderedTabs)
        tab_names = list(config.keys())

        # Home should come before View, View before Export
        assert tab_names.index("Home") < tab_names.index("View")
        assert tab_names.index("View") < tab_names.index("Export")

    def test_action_with_optional_fields(self):
        """Test action with optional icon and shortcut"""

        class WithOptional:
            def action(self):
                pass

            action._ribbon_action = {
                "tab": "Home",
                "group": "File",
                "label": "Save",
                "action": "save",
                "tooltip": "Save project",
                "icon": "save.png",
                "shortcut": "Ctrl+S",
            }

        config = build_ribbon_config(WithOptional)

        action = config["Home"]["groups"][0]["actions"][0]
        assert action["icon"] == "save.png"
        assert action["shortcut"] == "Ctrl+S"

    def test_action_without_optional_fields(self):
        """Test action without optional icon and shortcut"""

        class WithoutOptional:
            def action(self):
                pass

            action._ribbon_action = {
                "tab": "Home",
                "group": "File",
                "label": "Action",
                "action": "action",
                "tooltip": "Does stuff",
            }

        config = build_ribbon_config(WithoutOptional)

        action = config["Home"]["groups"][0]["actions"][0]
        assert action.get("icon") is None
        assert action.get("shortcut") is None

    def test_custom_tab_not_in_order(self):
        """Test custom tab not in predefined order"""

        class CustomTab:
            def action(self):
                pass

            action._ribbon_action = {
                "tab": "CustomTab",
                "group": "CustomGroup",
                "label": "Custom",
                "action": "action",
                "tooltip": "Custom action",
            }

        config = build_ribbon_config(CustomTab)

        assert "CustomTab" in config

    def test_inherited_actions(self):
        """Test that actions from parent classes are included"""

        class BaseClass:
            def base_action(self):
                pass

            base_action._ribbon_action = {
                "tab": "Home",
                "group": "File",
                "label": "Base Action",
                "action": "base_action",
                "tooltip": "From base",
            }

        class DerivedClass(BaseClass):
            def derived_action(self):
                pass

            derived_action._ribbon_action = {
                "tab": "Home",
                "group": "Edit",
                "label": "Derived Action",
                "action": "derived_action",
                "tooltip": "From derived",
            }

        config = build_ribbon_config(DerivedClass)

        # Should have both actions
        all_actions = []
        for group in config["Home"]["groups"]:
            all_actions.extend(group["actions"])

        action_names = [a["action"] for a in all_actions]
        assert "base_action" in action_names
        assert "derived_action" in action_names


class TestGetKeyboardShortcuts:
    """Tests for get_keyboard_shortcuts function"""

    def test_empty_class(self):
        """Test with a class that has no shortcuts"""

        class NoShortcuts:
            pass

        shortcuts = get_keyboard_shortcuts(NoShortcuts)
        assert shortcuts == {}

    def test_single_shortcut(self):
        """Test with a single shortcut"""

        class SingleShortcut:
            def save(self):
                pass

            save._ribbon_action = {
                "tab": "Home",
                "group": "File",
                "label": "Save",
                "action": "save",
                "tooltip": "Save",
                "shortcut": "Ctrl+S",
            }

        shortcuts = get_keyboard_shortcuts(SingleShortcut)

        assert "Ctrl+S" in shortcuts
        assert shortcuts["Ctrl+S"] == "save"

    def test_multiple_shortcuts(self):
        """Test with multiple shortcuts"""

        class MultiShortcut:
            def save(self):
                pass

            save._ribbon_action = {
                "tab": "Home",
                "group": "File",
                "label": "Save",
                "action": "save",
                "tooltip": "Save",
                "shortcut": "Ctrl+S",
            }

            def undo(self):
                pass

            undo._ribbon_action = {
                "tab": "Home",
                "group": "Edit",
                "label": "Undo",
                "action": "undo",
                "tooltip": "Undo",
                "shortcut": "Ctrl+Z",
            }

        shortcuts = get_keyboard_shortcuts(MultiShortcut)

        assert len(shortcuts) == 2
        assert shortcuts["Ctrl+S"] == "save"
        assert shortcuts["Ctrl+Z"] == "undo"

    def test_action_without_shortcut_ignored(self):
        """Test that actions without shortcuts are not included"""

        class MixedShortcuts:
            def with_shortcut(self):
                pass

            with_shortcut._ribbon_action = {
                "tab": "Home",
                "group": "File",
                "label": "With",
                "action": "with_shortcut",
                "tooltip": "Has shortcut",
                "shortcut": "Ctrl+W",
            }

            def without_shortcut(self):
                pass

            without_shortcut._ribbon_action = {
                "tab": "Home",
                "group": "File",
                "label": "Without",
                "action": "without_shortcut",
                "tooltip": "No shortcut",
            }

        shortcuts = get_keyboard_shortcuts(MixedShortcuts)

        assert len(shortcuts) == 1
        assert "Ctrl+W" in shortcuts


class TestValidateRibbonConfig:
    """Tests for validate_ribbon_config function"""

    def test_valid_config(self):
        """Test with a valid configuration"""
        config = {
            "Home": {
                "groups": [
                    {
                        "name": "File",
                        "actions": [
                            {
                                "label": "Save",
                                "action": "save",
                                "tooltip": "Save project",
                            }
                        ],
                    }
                ]
            }
        }

        errors = validate_ribbon_config(config)
        assert errors == []

    def test_empty_config(self):
        """Test with empty config"""
        errors = validate_ribbon_config({})
        assert errors == []

    def test_config_not_dict(self):
        """Test with non-dict config"""
        errors = validate_ribbon_config("not a dict")
        assert len(errors) == 1
        assert "must be a dictionary" in errors[0]

    def test_tab_data_not_dict(self):
        """Test with tab data that is not a dict"""
        config = {"Home": "not a dict"}

        errors = validate_ribbon_config(config)
        assert len(errors) == 1
        assert "Tab 'Home' data must be a dictionary" in errors[0]

    def test_missing_groups_key(self):
        """Test with missing 'groups' key"""
        config = {"Home": {"other_key": []}}

        errors = validate_ribbon_config(config)
        assert len(errors) == 1
        assert "missing 'groups' key" in errors[0]

    def test_groups_not_list(self):
        """Test with groups that is not a list"""
        config = {"Home": {"groups": "not a list"}}

        errors = validate_ribbon_config(config)
        assert len(errors) == 1
        assert "groups must be a list" in errors[0]

    def test_group_not_dict(self):
        """Test with group that is not a dict"""
        config = {"Home": {"groups": ["not a dict"]}}

        errors = validate_ribbon_config(config)
        assert len(errors) == 1
        assert "group 0 must be a dictionary" in errors[0]

    def test_group_missing_name(self):
        """Test with group missing name"""
        config = {"Home": {"groups": [{"actions": []}]}}

        errors = validate_ribbon_config(config)
        assert any("missing 'name'" in e for e in errors)

    def test_group_missing_actions(self):
        """Test with group missing actions"""
        config = {"Home": {"groups": [{"name": "File"}]}}

        errors = validate_ribbon_config(config)
        assert any("missing 'actions'" in e for e in errors)

    def test_actions_not_list(self):
        """Test with actions that is not a list"""
        config = {"Home": {"groups": [{"name": "File", "actions": "not a list"}]}}

        errors = validate_ribbon_config(config)
        assert any("actions must be a list" in e for e in errors)

    def test_action_not_dict(self):
        """Test with action that is not a dict"""
        config = {"Home": {"groups": [{"name": "File", "actions": ["not a dict"]}]}}

        errors = validate_ribbon_config(config)
        assert any("action 0 must be a dictionary" in e for e in errors)

    def test_action_missing_required_keys(self):
        """Test with action missing required keys"""
        config = {
            "Home": {
                "groups": [
                    {
                        "name": "File",
                        "actions": [
                            {
                                "label": "Save"
                                # missing 'action' and 'tooltip'
                            }
                        ],
                    }
                ]
            }
        }

        errors = validate_ribbon_config(config)
        assert any("missing 'action'" in e for e in errors)
        assert any("missing 'tooltip'" in e for e in errors)

    def test_multiple_errors(self):
        """Test that multiple errors are collected"""
        config = {
            "Tab1": {"groups": [{"name": "Group1", "actions": [{"label": "A"}]}]},  # missing action and tooltip
            "Tab2": {"groups": "not a list"},
        }

        errors = validate_ribbon_config(config)
        assert len(errors) >= 3  # At least: missing action, missing tooltip, groups not list


class TestPrintRibbonSummary:
    """Tests for print_ribbon_summary function"""

    def test_print_empty_config(self):
        """Test printing empty config"""
        config = {}

        with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
            print_ribbon_summary(config)
            output = mock_stdout.getvalue()

        assert "Total Tabs: 0" in output
        assert "Total Groups: 0" in output
        assert "Total Actions: 0" in output

    def test_print_single_tab(self):
        """Test printing single tab config"""
        config = {
            "Home": {
                "groups": [
                    {
                        "name": "File",
                        "actions": [
                            {
                                "label": "Save",
                                "action": "save",
                                "tooltip": "Save",
                            }
                        ],
                    }
                ]
            }
        }

        with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
            print_ribbon_summary(config)
            output = mock_stdout.getvalue()

        assert "Total Tabs: 1" in output
        assert "Total Groups: 1" in output
        assert "Total Actions: 1" in output
        assert "Home" in output
        assert "File" in output
        assert "Save" in output

    def test_print_with_shortcuts(self):
        """Test printing actions with shortcuts"""
        config = {
            "Home": {
                "groups": [
                    {
                        "name": "File",
                        "actions": [
                            {
                                "label": "Save",
                                "action": "save",
                                "tooltip": "Save",
                                "shortcut": "Ctrl+S",
                            }
                        ],
                    }
                ]
            }
        }

        with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
            print_ribbon_summary(config)
            output = mock_stdout.getvalue()

        assert "(Ctrl+S)" in output

    def test_print_multiple_tabs_and_groups(self):
        """Test printing config with multiple tabs and groups"""
        config = {
            "Home": {
                "groups": [
                    {
                        "name": "File",
                        "actions": [
                            {"label": "New", "action": "new", "tooltip": "New"},
                            {"label": "Open", "action": "open", "tooltip": "Open"},
                        ],
                    },
                    {
                        "name": "Edit",
                        "actions": [
                            {"label": "Undo", "action": "undo", "tooltip": "Undo"},
                        ],
                    },
                ]
            },
            "View": {
                "groups": [
                    {
                        "name": "Zoom",
                        "actions": [
                            {"label": "Zoom In", "action": "zoom_in", "tooltip": "Zoom In"},
                        ],
                    }
                ]
            },
        }

        with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
            print_ribbon_summary(config)
            output = mock_stdout.getvalue()

        assert "Total Tabs: 2" in output
        assert "Total Groups: 3" in output
        assert "Total Actions: 4" in output
