#!/usr/bin/python3
# -*- coding: utf-8 -*-

import pytest
from unittest.mock import patch

from sun.cli import Cli, main
from sun.utils import Utilities


class TestViewUpdates:

    def test_view_changelog_success(self, capsys):
        cli = Cli()
        with patch.object(cli.utils, 'read_repo_text_file', return_value='Mon Jan 01 2026\n...'):
            with patch('pydoc.pager') as mock_pager:
                cli.view_changelog()
                mock_pager.assert_called_once()

    def test_view_changelog_fetch_failure(self, capsys):
        cli = Cli()
        with patch.object(cli.utils, 'read_repo_text_file', return_value=''):
            cli.view_changelog()
        assert 'Error' in capsys.readouterr().out

    def test_no_packages(self, capsys):
        cli = Cli()
        with patch.object(cli, 'check_updates', return_value=('No news is good news!', [])):
            cli.view_updates()
        assert 'No news is good news!' in capsys.readouterr().out

    def test_with_packages(self, capsys):
        cli = Cli()
        pkgs = ['vim-9.0-x86_64-1.txz']
        with patch.object(cli, 'check_updates', return_value=('1 software updates\n', pkgs)):
            cli.view_updates()
        output = capsys.readouterr().out
        assert 'vim' in output

    def test_pattern_match(self, capsys):
        cli = Cli()
        pkgs = ['vim-9.0-x86_64-1.txz', 'curl-7.85-x86_64-1.txz']
        with patch.object(cli, 'check_updates', return_value=('2 software updates\n', pkgs)):
            cli.view_updates(pattern='vim')
        assert 'vim' in capsys.readouterr().out

    def test_pattern_no_match(self, capsys):
        cli = Cli()
        pkgs = ['vim-9.0-x86_64-1.txz', 'curl-7.85-x86_64-1.txz']
        with patch.object(cli, 'check_updates', return_value=('2 software updates\n', pkgs)):
            cli.view_updates(pattern='kernel')
        assert 'No news is good news!' in capsys.readouterr().out


class TestSu:

    def test_root_raises_system_exit(self):
        with patch('getpass.getuser', return_value='root'):
            with pytest.raises(SystemExit):
                Cli.su()

    def test_non_root_passes(self):
        with patch('getpass.getuser', return_value='testuser'):
            Cli.su()  # Should not raise


class TestViewStart:

    def test_shows_pid_on_success(self, capsys):
        cli = Cli()
        message = 'Starting SUN daemon:  /usr/bin/sun-daemon'
        with patch.object(cli, 'daemon_process', return_value=message):
            with patch.object(cli.utils, 'get_daemon_pid', return_value='1234'):
                with patch('time.sleep'):
                    cli.view_start()
        out = capsys.readouterr().out
        assert 'PID' in out and '1234' in out

    def test_already_running(self, capsys):
        cli = Cli()
        with patch.object(cli, 'daemon_process', return_value='SUN is already running'):
            with patch.object(cli.utils, 'get_daemon_pid', return_value='1234'):
                cli.view_start()
        out = capsys.readouterr().out
        assert 'already running' in out
        assert 'PID' in out and '1234' in out
        assert 'Done' not in out


class TestViewStop:

    def test_shows_done_on_success(self, capsys):
        cli = Cli()
        with patch.object(cli.utils, 'get_daemon_pid', return_value='1234'):
            with patch.object(cli, 'daemon_process', return_value='Stopping SUN daemon:  /usr/bin/sun-daemon (PID 1234)...'):
                with patch('time.sleep'):
                    cli.view_stop()
        assert 'Done' in capsys.readouterr().out


class TestViewRestart:

    def test_shows_new_pid_on_success(self, capsys):
        cli = Cli()
        message = 'Restarting SUN daemon:  /usr/bin/sun-daemon'
        with patch.object(cli, 'daemon_process', return_value=message):
            with patch.object(cli.utils, 'get_daemon_pid', return_value='5678'):
                with patch('time.sleep'):
                    cli.view_restart()
        out = capsys.readouterr().out
        assert 'PID' in out and '5678' in out


class TestViewStatus:

    def test_running(self, capsys):
        cli = Cli()
        with patch.object(cli.utils, 'get_daemon_pid', return_value='1234'):
            with patch.object(cli, 'daemon_status', return_value=True):
                cli.view_status()
        out = capsys.readouterr().out
        assert 'PID' in out and '1234' in out

    def test_not_running(self, capsys):
        cli = Cli()
        with patch.object(cli.utils, 'get_daemon_pid', return_value=''):
            with patch.object(cli, 'daemon_status', return_value=False):
                cli.view_status()
        assert 'not running' in capsys.readouterr().out


class TestViewOsInfo:

    def test_strips_html(self, capsys):
        cli = Cli()
        with patch.object(cli.utils, 'os_info_html', return_value='<b>OS</b>: Slackware<br>'):
            cli.view_os_info()
        assert 'Slackware' in capsys.readouterr().out
        assert '<b>' not in capsys.readouterr().out


class TestMenu:

    def test_no_args_exits(self):
        cli = Cli()
        with patch('sys.argv', ['sun']):
            with patch('getpass.getuser', return_value='testuser'):
                with pytest.raises(SystemExit):
                    cli.menu()

    def test_start_command_dispatches(self):
        cli = Cli()
        with patch('sys.argv', ['sun', 'start']):
            with patch('getpass.getuser', return_value='testuser'):
                with patch.object(cli, 'view_start') as mock_start:
                    cli.menu()
                    mock_start.assert_called_once()

    def test_changelog_command_dispatches(self):
        cli = Cli()
        with patch('sys.argv', ['sun', 'changelog']):
            with patch('getpass.getuser', return_value='testuser'):
                with patch.object(cli, 'view_changelog') as mock_changelog:
                    cli.menu()
                    mock_changelog.assert_called_once()

    def test_view_log_success(self, tmp_path):
        log_file = tmp_path / 'sun-daemon.log'
        log_file.write_text('2026-03-22 INFO sun-daemon started', encoding='utf-8')
        cli = Cli()
        with patch('sun.cli.LOG_FILE', log_file):
            with patch('pydoc.pager') as mock_pager:
                cli.view_log()
                mock_pager.assert_called_once()

    def test_view_log_missing(self, tmp_path, capsys):
        cli = Cli()
        with patch('sun.cli.LOG_FILE', tmp_path / 'nonexistent.log'):
            cli.view_log()
        assert 'not found' in capsys.readouterr().out

    def test_log_command_dispatches(self):
        cli = Cli()
        with patch('sys.argv', ['sun', 'log']):
            with patch('getpass.getuser', return_value='testuser'):
                with patch.object(cli, 'view_log') as mock_log:
                    cli.menu()
                    mock_log.assert_called_once()

    def test_start_tray_launches_popen(self):
        cli = Cli()
        with patch('sys.argv', ['sun', 'start', '--tray']):
            with patch('getpass.getuser', return_value='testuser'):
                with patch('subprocess.Popen') as mock_popen:
                    cli.menu()
                    mock_popen.assert_called_once()


class TestMirrorVersionWarning:

    def _make_utils(self, mirror: str) -> Utilities:
        with patch('sun.utils.configs_load') as mock_configs:
            mock_configs.repo_mirror = mirror
            return Utilities()

    def test_no_warning_when_mirror_matches_stable(self):
        utils = self._make_utils('https://example.com/slackware64-15.0/')
        with patch('pathlib.Path.read_text', return_value='Slackware 15.0'):
            result = utils.mirror_version_warning()
        assert result == ''

    def test_no_warning_when_mirror_matches_current(self):
        utils = self._make_utils('https://example.com/slackware64-current/')
        with patch('pathlib.Path.read_text', return_value='Slackware 15.0+'):
            result = utils.mirror_version_warning()
        assert result == ''

    def test_warning_when_stable_mirror_but_current_installed(self):
        utils = self._make_utils('https://example.com/slackware64-15.0/')
        with patch('pathlib.Path.read_text', return_value='Slackware 15.0+'):
            result = utils.mirror_version_warning()
        assert 'Warning' in result
        assert 'current' in result

    def test_warning_when_current_mirror_but_stable_installed(self):
        utils = self._make_utils('https://example.com/slackware64-current/')
        with patch('pathlib.Path.read_text', return_value='Slackware 15.0'):
            result = utils.mirror_version_warning()
        assert 'Warning' in result
        assert '15.0' in result

    def test_no_warning_on_oserror(self):
        utils = self._make_utils('https://example.com/slackware64-15.0/')
        with patch('pathlib.Path.read_text', side_effect=OSError):
            result = utils.mirror_version_warning()
        assert result == ''

    def test_warning_printed_in_view_updates(self, capsys):
        cli = Cli()
        with patch.object(cli.utils, 'mirror_version_warning', return_value='Warning: mirror may not match'):
            with patch.object(cli, 'check_updates', return_value=('No news is good news!', [])):
                cli.view_updates()
        assert 'Warning' in capsys.readouterr().out


class TestSunArgumentParser:

    def test_unknown_command_error_message(self, capsys):
        cli = Cli()
        with patch('sys.argv', ['sun', 'badcmd']):
            with patch('getpass.getuser', return_value='testuser'):
                with pytest.raises(SystemExit):
                    cli.menu()
        out = capsys.readouterr().out
        assert "Unknown command 'badcmd'" in out
        assert 'sun -h' in out


class TestViewConfig:

    def test_shows_all_sections(self, capsys):
        cli = Cli()
        cli.view_config()
        out = capsys.readouterr().out
        assert '[TIME]' in out
        assert '[REPOSITORY]' in out
        assert '[TRAY_ICON]' in out

    def test_shows_key_values(self, capsys):
        cli = Cli()
        cli.view_config()
        out = capsys.readouterr().out
        assert 'INTERVAL' in out
        assert 'HTTP_MIRROR' in out
        assert 'LOG_FONT_SIZE' in out

    def test_config_command_dispatches(self):
        cli = Cli()
        with patch('sys.argv', ['sun', 'config']):
            with patch('getpass.getuser', return_value='testuser'):
                with patch.object(cli, 'view_config') as mock_config:
                    cli.menu()
                    mock_config.assert_called_once()


class TestMain:

    def test_keyboard_interrupt_exits(self):
        with patch.object(Cli, 'menu', side_effect=KeyboardInterrupt):
            with pytest.raises(SystemExit):
                main()


class TestColorizePackage:

    def test_upgraded_color_applied(self):
        cli = Cli()
        result = cli._colorize_package('vim-9.0-x86_64-1.txz:  Upgraded.', 30)
        assert cli.ACTION_COLORS['Upgraded'] in result
        assert 'Upgraded.' in result

    def test_removed_color_applied(self):
        cli = Cli()
        result = cli._colorize_package('foo-1.0.txz:  Removed.', 20)
        assert cli.ACTION_COLORS['Removed'] in result

    def test_unknown_action_no_color(self):
        cli = Cli()
        result = cli._colorize_package('foo-1.0.txz:  CustomAction.', 20)
        assert '\x1b[' not in result
        assert 'CustomAction.' in result

    def test_malformed_line_returned_unchanged(self):
        cli = Cli()
        result = cli._colorize_package('no-double-space-here')
        assert result == 'no-double-space-here'

    def test_name_padded_to_max_len(self):
        cli = Cli()
        result = cli._colorize_package('vim-9.0.txz:  Upgraded.', 40)
        # name part should be left-padded to 40 chars
        assert result.startswith('vim-9.0.txz:'.ljust(40))


class TestPrintResult:

    def test_failed_result_printed(self, capsys):
        cli = Cli()
        with patch('time.sleep'):
            cli.print_result('FAILED [1]: something')
        assert 'FAILED' in capsys.readouterr().out

    def test_success_result_prints_done(self, capsys):
        cli = Cli()
        with patch('time.sleep'):
            cli.print_result('Starting SUN daemon...')
        assert 'Done' in capsys.readouterr().out
